1 /*
2  * tkButton.c --
3  *
4  *	This module implements a collection of button-like
5  *	widgets for the Tk toolkit.  The widgets implemented
6  *	include labels, buttons, check buttons, and radio
7  *	buttons.
8  *
9  * Copyright (c) 1990-1994 The Regents of the University of California.
10  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  *
15  * SCCS: @(#) tkButton.c 1.128 96/03/01 17:34:49
16  */
17 
18 #include "tkInt.h"
19 #include "tkDefault.h"
20 
21 /*
22  * A data structure of the following type is kept for each
23  * widget managed by this file:
24  */
25 
26 typedef struct {
27     Tk_Window tkwin;		/* Window that embodies the button.  NULL
28 				 * means that the window has been destroyed. */
29     Display *display;		/* Display containing widget.  Needed to
30 				 * free up resources after tkwin is gone. */
31     Tcl_Interp *interp;		/* Interpreter associated with button. */
32     Tcl_Command widgetCmd;	/* Token for button's widget command. */
33     int type;			/* Type of widget:  restricts operations
34 				 * that may be performed on widget.  See
35 				 * below for possible values. */
36 
37     /*
38      * Information about what's in the button.
39      */
40 
41     char *text;			/* Text to display in button (malloc'ed)
42 				 * or NULL. */
43     int numChars;		/* # of characters in text. */
44     int underline;		/* Index of character to underline.  < 0 means
45 				 * don't underline anything. */
46     char *textVarName;		/* Name of variable (malloc'ed) or NULL.
47 				 * If non-NULL, button displays the contents
48 				 * of this variable. */
49     Pixmap bitmap;		/* Bitmap to display or None.  If not None
50 				 * then text and textVar are ignored. */
51     char *imageString;		/* Name of image to display (malloc'ed), or
52 				 * NULL.  If non-NULL, bitmap, text, and
53 				 * textVarName are ignored. */
54     Tk_Image image;		/* Image to display in window, or NULL if
55 				 * none. */
56     char *selectImageString;	/* Name of image to display when selected
57 				 * (malloc'ed), or NULL. */
58     Tk_Image selectImage;	/* Image to display in window when selected,
59 				 * or NULL if none.  Ignored if image is
60 				 * NULL. */
61 
62     /*
63      * Information used when displaying widget:
64      */
65 
66     Tk_Uid state;		/* State of button for display purposes:
67 				 * normal, active, or disabled. */
68     Tk_3DBorder normalBorder;	/* Structure used to draw 3-D
69 				 * border and background when window
70 				 * isn't active.  NULL means no such
71 				 * border exists. */
72     Tk_3DBorder activeBorder;	/* Structure used to draw 3-D
73 				 * border and background when window
74 				 * is active.  NULL means no such
75 				 * border exists. */
76     int borderWidth;		/* Width of border. */
77     int relief;			/* 3-d effect: TK_RELIEF_RAISED, etc. */
78     int highlightWidth;		/* Width in pixels of highlight to draw
79 				 * around widget when it has the focus.
80 				 * <= 0 means don't draw a highlight. */
81     XColor *highlightBgColorPtr;
82 				/* Color for drawing traversal highlight
83 				 * area when highlight is off. */
84     XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
85     int inset;			/* Total width of all borders, including
86 				 * traversal highlight and 3-D border.
87 				 * Indicates how much interior stuff must
88 				 * be offset from outside edges to leave
89 				 * room for borders. */
90     XFontStruct *fontPtr;	/* Information about text font, or NULL. */
91     XColor *normalFg;		/* Foreground color in normal mode. */
92     XColor *activeFg;		/* Foreground color in active mode.  NULL
93 				 * means use normalFg instead. */
94     XColor *disabledFg;		/* Foreground color when disabled.  NULL
95 				 * means use normalFg with a 50% stipple
96 				 * instead. */
97     GC normalTextGC;		/* GC for drawing text in normal mode.  Also
98 				 * used to copy from off-screen pixmap onto
99 				 * screen. */
100     GC activeTextGC;		/* GC for drawing text in active mode (NULL
101 				 * means use normalTextGC). */
102     Pixmap gray;		/* Pixmap for displaying disabled text if
103 				 * disabledFg is NULL. */
104     GC disabledGC;		/* Used to produce disabled effect.  If
105 				 * disabledFg isn't NULL, this GC is used to
106 				 * draw button text or icon.  Otherwise
107 				 * text or icon is drawn with normalGC and
108 				 * this GC is used to stipple background
109 				 * across it.  For labels this is None. */
110     GC copyGC;			/* Used for copying information from an
111 				 * off-screen pixmap to the screen. */
112     char *widthString;		/* Value of -width option.  Malloc'ed. */
113     char *heightString;		/* Value of -height option.  Malloc'ed. */
114     int width, height;		/* If > 0, these specify dimensions to request
115 				 * for window, in characters for text and in
116 				 * pixels for bitmaps.  In this case the actual
117 				 * size of the text string or bitmap is
118 				 * ignored in computing desired window size. */
119     int wrapLength;		/* Line length (in pixels) at which to wrap
120 				 * onto next line.  <= 0 means don't wrap
121 				 * except at newlines. */
122     int padX, padY;		/* Extra space around text (pixels to leave
123 				 * on each side).  Ignored for bitmaps and
124 				 * images. */
125     Tk_Anchor anchor;		/* Where text/bitmap should be displayed
126 				 * inside button region. */
127     Tk_Justify justify;		/* Justification to use for multi-line text. */
128     int indicatorOn;		/* True means draw indicator, false means
129 				 * don't draw it. */
130     Tk_3DBorder selectBorder;	/* For drawing indicator background, or perhaps
131 				 * widget background, when selected. */
132     int textWidth;		/* Width needed to display text as requested,
133 				 * in pixels. */
134     int textHeight;		/* Height needed to display text as requested,
135 				 * in pixels. */
136     int indicatorSpace;		/* Horizontal space (in pixels) allocated for
137 				 * display of indicator. */
138     int indicatorDiameter;	/* Diameter of indicator, in pixels. */
139 
140     /*
141      * For check and radio buttons, the fields below are used
142      * to manage the variable indicating the button's state.
143      */
144 
145     char *selVarName;		/* Name of variable used to control selected
146 				 * state of button.  Malloc'ed (if
147 				 * not NULL). */
148     char *onValue;		/* Value to store in variable when
149 				 * this button is selected.  Malloc'ed (if
150 				 * not NULL). */
151     char *offValue;		/* Value to store in variable when this
152 				 * button isn't selected.  Malloc'ed
153 				 * (if not NULL).  Valid only for check
154 				 * buttons. */
155 
156     /*
157      * Miscellaneous information:
158      */
159 
160     Tk_Cursor cursor;		/* Current cursor for window, or None. */
161     char *takeFocus;		/* Value of -takefocus option;  not used in
162 				 * the C code, but used by keyboard traversal
163 				 * scripts.  Malloc'ed, but may be NULL. */
164     char *command;		/* Command to execute when button is
165 				 * invoked; valid for buttons only.
166 				 * If not NULL, it's malloc-ed. */
167     int flags;			/* Various flags;  see below for
168 				 * definitions. */
169 } Button;
170 
171 /*
172  * Possible "type" values for buttons.  These are the kinds of
173  * widgets supported by this file.  The ordering of the type
174  * numbers is significant:  greater means more features and is
175  * used in the code.
176  */
177 
178 #define TYPE_LABEL		0
179 #define TYPE_BUTTON		1
180 #define TYPE_CHECK_BUTTON	2
181 #define TYPE_RADIO_BUTTON	3
182 
183 /*
184  * Class names for buttons, indexed by one of the type values above.
185  */
186 
187 static char *classNames[] = {"Label", "Button", "Checkbutton", "Radiobutton"};
188 
189 /*
190  * Flag bits for buttons:
191  *
192  * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
193  *				has already been queued to redraw
194  *				this window.
195  * SELECTED:			Non-zero means this button is selected,
196  *				so special highlight should be drawn.
197  * GOT_FOCUS:			Non-zero means this button currently
198  *				has the input focus.
199  */
200 
201 #define REDRAW_PENDING		1
202 #define SELECTED		2
203 #define GOT_FOCUS		4
204 
205 /*
206  * Mask values used to selectively enable entries in the
207  * configuration specs:
208  */
209 
210 #define LABEL_MASK		TK_CONFIG_USER_BIT
211 #define BUTTON_MASK		TK_CONFIG_USER_BIT << 1
212 #define CHECK_BUTTON_MASK	TK_CONFIG_USER_BIT << 2
213 #define RADIO_BUTTON_MASK	TK_CONFIG_USER_BIT << 3
214 #define ALL_MASK		(LABEL_MASK | BUTTON_MASK \
215 	| CHECK_BUTTON_MASK | RADIO_BUTTON_MASK)
216 
217 static int configFlags[] = {LABEL_MASK, BUTTON_MASK,
218 	CHECK_BUTTON_MASK, RADIO_BUTTON_MASK};
219 
220 /*
221  * Information used for parsing configuration specs:
222  */
223 
224 static Tk_ConfigSpec configSpecs[] = {
225     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
226 	DEF_BUTTON_ACTIVE_BG_COLOR, Tk_Offset(Button, activeBorder),
227 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK
228 	|TK_CONFIG_COLOR_ONLY},
229     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
230 	DEF_BUTTON_ACTIVE_BG_MONO, Tk_Offset(Button, activeBorder),
231 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK
232 	|TK_CONFIG_MONO_ONLY},
233     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
234 	DEF_BUTTON_ACTIVE_FG_COLOR, Tk_Offset(Button, activeFg),
235 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK
236 	|TK_CONFIG_COLOR_ONLY},
237     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
238 	DEF_BUTTON_ACTIVE_FG_MONO, Tk_Offset(Button, activeFg),
239 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK
240 	|TK_CONFIG_MONO_ONLY},
241     {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
242 	DEF_BUTTON_ANCHOR, Tk_Offset(Button, anchor), ALL_MASK},
243     {TK_CONFIG_BORDER, "-background", "background", "Background",
244 	DEF_BUTTON_BG_COLOR, Tk_Offset(Button, normalBorder),
245 	ALL_MASK | TK_CONFIG_COLOR_ONLY},
246     {TK_CONFIG_BORDER, "-background", "background", "Background",
247 	DEF_BUTTON_BG_MONO, Tk_Offset(Button, normalBorder),
248 	ALL_MASK | TK_CONFIG_MONO_ONLY},
249     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
250 	(char *) NULL, 0, ALL_MASK},
251     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
252 	(char *) NULL, 0, ALL_MASK},
253     {TK_CONFIG_BITMAP, "-bitmap", "bitmap", "Bitmap",
254 	DEF_BUTTON_BITMAP, Tk_Offset(Button, bitmap),
255 	ALL_MASK|TK_CONFIG_NULL_OK},
256     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
257 	DEF_BUTTON_BORDER_WIDTH, Tk_Offset(Button, borderWidth), ALL_MASK},
258     {TK_CONFIG_STRING, "-command", "command", "Command",
259 	DEF_BUTTON_COMMAND, Tk_Offset(Button, command),
260 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
261     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
262 	DEF_BUTTON_CURSOR, Tk_Offset(Button, cursor),
263 	ALL_MASK|TK_CONFIG_NULL_OK},
264     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
265 	"DisabledForeground", DEF_BUTTON_DISABLED_FG_COLOR,
266 	Tk_Offset(Button, disabledFg), BUTTON_MASK|CHECK_BUTTON_MASK
267 	|RADIO_BUTTON_MASK|TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
268     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
269 	"DisabledForeground", DEF_BUTTON_DISABLED_FG_MONO,
270 	Tk_Offset(Button, disabledFg), BUTTON_MASK|CHECK_BUTTON_MASK
271 	|RADIO_BUTTON_MASK|TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
272     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
273 	(char *) NULL, 0, ALL_MASK},
274     {TK_CONFIG_FONT, "-font", "font", "Font",
275 	DEF_BUTTON_FONT, Tk_Offset(Button, fontPtr),
276 	ALL_MASK},
277     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
278 	DEF_BUTTON_FG, Tk_Offset(Button, normalFg), ALL_MASK},
279     {TK_CONFIG_STRING, "-height", "height", "Height",
280 	DEF_BUTTON_HEIGHT, Tk_Offset(Button, heightString), ALL_MASK},
281     {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
282 	"HighlightBackground", DEF_BUTTON_HIGHLIGHT_BG,
283 	Tk_Offset(Button, highlightBgColorPtr), ALL_MASK},
284     {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
285 	DEF_BUTTON_HIGHLIGHT, Tk_Offset(Button, highlightColorPtr),
286 	ALL_MASK},
287     {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
288 	"HighlightThickness",
289 	DEF_LABEL_HIGHLIGHT_WIDTH, Tk_Offset(Button, highlightWidth),
290 	LABEL_MASK},
291     {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
292 	"HighlightThickness",
293 	DEF_BUTTON_HIGHLIGHT_WIDTH, Tk_Offset(Button, highlightWidth),
294 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK},
295     {TK_CONFIG_STRING, "-image", "image", "Image",
296 	DEF_BUTTON_IMAGE, Tk_Offset(Button, imageString),
297 	ALL_MASK|TK_CONFIG_NULL_OK},
298     {TK_CONFIG_BOOLEAN, "-indicatoron", "indicatorOn", "IndicatorOn",
299 	DEF_BUTTON_INDICATOR, Tk_Offset(Button, indicatorOn),
300 	CHECK_BUTTON_MASK|RADIO_BUTTON_MASK},
301     {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
302 	DEF_BUTTON_JUSTIFY, Tk_Offset(Button, justify), ALL_MASK},
303     {TK_CONFIG_STRING, "-offvalue", "offValue", "Value",
304 	DEF_BUTTON_OFF_VALUE, Tk_Offset(Button, offValue),
305 	CHECK_BUTTON_MASK},
306     {TK_CONFIG_STRING, "-onvalue", "onValue", "Value",
307 	DEF_BUTTON_ON_VALUE, Tk_Offset(Button, onValue),
308 	CHECK_BUTTON_MASK},
309     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
310 	DEF_BUTTON_PADX, Tk_Offset(Button, padX), BUTTON_MASK},
311     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
312 	DEF_LABCHKRAD_PADX, Tk_Offset(Button, padX),
313 	LABEL_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK},
314     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
315 	DEF_BUTTON_PADY, Tk_Offset(Button, padY), BUTTON_MASK},
316     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
317 	DEF_LABCHKRAD_PADY, Tk_Offset(Button, padY),
318 	LABEL_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK},
319     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
320 	DEF_BUTTON_RELIEF, Tk_Offset(Button, relief), BUTTON_MASK},
321     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
322 	DEF_LABCHKRAD_RELIEF, Tk_Offset(Button, relief),
323 	LABEL_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK},
324     {TK_CONFIG_BORDER, "-selectcolor", "selectColor", "Background",
325 	DEF_BUTTON_SELECT_COLOR, Tk_Offset(Button, selectBorder),
326 	CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_COLOR_ONLY
327 	|TK_CONFIG_NULL_OK},
328     {TK_CONFIG_BORDER, "-selectcolor", "selectColor", "Background",
329 	DEF_BUTTON_SELECT_MONO, Tk_Offset(Button, selectBorder),
330 	CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_MONO_ONLY
331 	|TK_CONFIG_NULL_OK},
332     {TK_CONFIG_STRING, "-selectimage", "selectImage", "SelectImage",
333 	DEF_BUTTON_SELECT_IMAGE, Tk_Offset(Button, selectImageString),
334 	CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
335     {TK_CONFIG_UID, "-state", "state", "State",
336 	DEF_BUTTON_STATE, Tk_Offset(Button, state),
337 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK},
338     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
339 	DEF_LABEL_TAKE_FOCUS, Tk_Offset(Button, takeFocus),
340 	LABEL_MASK|TK_CONFIG_NULL_OK},
341     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
342 	DEF_BUTTON_TAKE_FOCUS, Tk_Offset(Button, takeFocus),
343 	BUTTON_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
344     {TK_CONFIG_STRING, "-text", "text", "Text",
345 	DEF_BUTTON_TEXT, Tk_Offset(Button, text), ALL_MASK},
346     {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
347 	DEF_BUTTON_TEXT_VARIABLE, Tk_Offset(Button, textVarName),
348 	ALL_MASK|TK_CONFIG_NULL_OK},
349     {TK_CONFIG_INT, "-underline", "underline", "Underline",
350 	DEF_BUTTON_UNDERLINE, Tk_Offset(Button, underline), ALL_MASK},
351     {TK_CONFIG_STRING, "-value", "value", "Value",
352 	DEF_BUTTON_VALUE, Tk_Offset(Button, onValue),
353 	RADIO_BUTTON_MASK},
354     {TK_CONFIG_STRING, "-variable", "variable", "Variable",
355 	DEF_RADIOBUTTON_VARIABLE, Tk_Offset(Button, selVarName),
356 	RADIO_BUTTON_MASK},
357     {TK_CONFIG_STRING, "-variable", "variable", "Variable",
358 	DEF_CHECKBUTTON_VARIABLE, Tk_Offset(Button, selVarName),
359 	CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK},
360     {TK_CONFIG_STRING, "-width", "width", "Width",
361 	DEF_BUTTON_WIDTH, Tk_Offset(Button, widthString), ALL_MASK},
362     {TK_CONFIG_PIXELS, "-wraplength", "wrapLength", "WrapLength",
363 	DEF_BUTTON_WRAP_LENGTH, Tk_Offset(Button, wrapLength), ALL_MASK},
364     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
365 	(char *) NULL, 0, 0}
366 };
367 
368 /*
369  * String to print out in error messages, identifying options for
370  * widget commands for different types of labels or buttons:
371  */
372 
373 static char *optionStrings[] = {
374     "cget or configure",
375     "cget, configure, flash, or invoke",
376     "cget, configure, deselect, flash, invoke, select, or toggle",
377     "cget, configure, deselect, flash, invoke, or select"
378 };
379 
380 /*
381  * Forward declarations for procedures defined later in this file:
382  */
383 
384 static void		ButtonCmdDeletedProc _ANSI_ARGS_((
385 			    ClientData clientData));
386 static int		ButtonCreate _ANSI_ARGS_((ClientData clientData,
387 			    Tcl_Interp *interp, int argc, char **argv,
388 			    int type));
389 static void		ButtonEventProc _ANSI_ARGS_((ClientData clientData,
390 			    XEvent *eventPtr));
391 static void		ButtonImageProc _ANSI_ARGS_((ClientData clientData,
392 			    int x, int y, int width, int height,
393 			    int imgWidth, int imgHeight));
394 static void		ButtonSelectImageProc _ANSI_ARGS_((
395 			    ClientData clientData, int x, int y, int width,
396 			    int height, int imgWidth, int imgHeight));
397 static char *		ButtonTextVarProc _ANSI_ARGS_((ClientData clientData,
398 			    Tcl_Interp *interp, char *name1, char *name2,
399 			    int flags));
400 static char *		ButtonVarProc _ANSI_ARGS_((ClientData clientData,
401 			    Tcl_Interp *interp, char *name1, char *name2,
402 			    int flags));
403 static int		ButtonWidgetCmd _ANSI_ARGS_((ClientData clientData,
404 			    Tcl_Interp *interp, int argc, char **argv));
405 static void		ComputeButtonGeometry _ANSI_ARGS_((Button *butPtr));
406 static int		ConfigureButton _ANSI_ARGS_((Tcl_Interp *interp,
407 			    Button *butPtr, int argc, char **argv,
408 			    int flags));
409 static void		DestroyButton _ANSI_ARGS_((Button *butPtr));
410 static void		DisplayButton _ANSI_ARGS_((ClientData clientData));
411 static int		InvokeButton  _ANSI_ARGS_((Button *butPtr));
412 
413 /*
414  *--------------------------------------------------------------
415  *
416  * Tk_ButtonCmd, Tk_CheckbuttonCmd, Tk_LabelCmd, Tk_RadiobuttonCmd --
417  *
418  *	These procedures are invoked to process the "button", "label",
419  *	"radiobutton", and "checkbutton" Tcl commands.  See the
420  *	user documentation for details on what they do.
421  *
422  * Results:
423  *	A standard Tcl result.
424  *
425  * Side effects:
426  *	See the user documentation.  These procedures are just wrappers;
427  *	they call ButtonCreate to do all of the real work.
428  *
429  *--------------------------------------------------------------
430  */
431 
432 int
Tk_ButtonCmd(clientData,interp,argc,argv)433 Tk_ButtonCmd(clientData, interp, argc, argv)
434     ClientData clientData;	/* Main window associated with
435 				 * interpreter. */
436     Tcl_Interp *interp;		/* Current interpreter. */
437     int argc;			/* Number of arguments. */
438     char **argv;		/* Argument strings. */
439 {
440     return ButtonCreate(clientData, interp, argc, argv, TYPE_BUTTON);
441 }
442 
443 int
Tk_CheckbuttonCmd(clientData,interp,argc,argv)444 Tk_CheckbuttonCmd(clientData, interp, argc, argv)
445     ClientData clientData;	/* Main window associated with
446 				 * interpreter. */
447     Tcl_Interp *interp;		/* Current interpreter. */
448     int argc;			/* Number of arguments. */
449     char **argv;		/* Argument strings. */
450 {
451     return ButtonCreate(clientData, interp, argc, argv, TYPE_CHECK_BUTTON);
452 }
453 
454 int
Tk_LabelCmd(clientData,interp,argc,argv)455 Tk_LabelCmd(clientData, interp, argc, argv)
456     ClientData clientData;	/* Main window associated with
457 				 * interpreter. */
458     Tcl_Interp *interp;		/* Current interpreter. */
459     int argc;			/* Number of arguments. */
460     char **argv;		/* Argument strings. */
461 {
462     return ButtonCreate(clientData, interp, argc, argv, TYPE_LABEL);
463 }
464 
465 int
Tk_RadiobuttonCmd(clientData,interp,argc,argv)466 Tk_RadiobuttonCmd(clientData, interp, argc, argv)
467     ClientData clientData;	/* Main window associated with
468 				 * interpreter. */
469     Tcl_Interp *interp;		/* Current interpreter. */
470     int argc;			/* Number of arguments. */
471     char **argv;		/* Argument strings. */
472 {
473     return ButtonCreate(clientData, interp, argc, argv, TYPE_RADIO_BUTTON);
474 }
475 
476 /*
477  *--------------------------------------------------------------
478  *
479  * ButtonCreate --
480  *
481  *	This procedure does all the real work of implementing the
482  *	"button", "label", "radiobutton", and "checkbutton" Tcl
483  *	commands.  See the user documentation for details on what it does.
484  *
485  * Results:
486  *	A standard Tcl result.
487  *
488  * Side effects:
489  *	See the user documentation.
490  *
491  *--------------------------------------------------------------
492  */
493 
494 static int
ButtonCreate(clientData,interp,argc,argv,type)495 ButtonCreate(clientData, interp, argc, argv, type)
496     ClientData clientData;	/* Main window associated with
497 				 * interpreter. */
498     Tcl_Interp *interp;		/* Current interpreter. */
499     int argc;			/* Number of arguments. */
500     char **argv;		/* Argument strings. */
501     int type;			/* Type of button to create: TYPE_LABEL,
502 				 * TYPE_BUTTON, TYPE_CHECK_BUTTON, or
503 				 * TYPE_RADIO_BUTTON. */
504 {
505     register Button *butPtr;
506     Tk_Window tkwin = (Tk_Window) clientData;
507     Tk_Window new;
508 
509     if (argc < 2) {
510 	Tcl_AppendResult(interp, "wrong # args: should be \"",
511 		argv[0], " pathName ?options?\"", (char *) NULL);
512 	return TCL_ERROR;
513     }
514 
515     /*
516      * Create the new window.
517      */
518 
519     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
520     if (new == NULL) {
521 	return TCL_ERROR;
522     }
523 
524     /*
525      * Initialize the data structure for the button.
526      */
527 
528     butPtr = (Button *) ckalloc(sizeof(Button));
529     butPtr->tkwin = new;
530     butPtr->display = Tk_Display(new);
531     butPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(butPtr->tkwin),
532 	    ButtonWidgetCmd, (ClientData) butPtr, ButtonCmdDeletedProc);
533     butPtr->interp = interp;
534     butPtr->type = type;
535     butPtr->text = NULL;
536     butPtr->numChars = 0;
537     butPtr->underline = -1;
538     butPtr->textVarName = NULL;
539     butPtr->bitmap = None;
540     butPtr->imageString = NULL;
541     butPtr->image = NULL;
542     butPtr->selectImageString = NULL;
543     butPtr->selectImage = NULL;
544     butPtr->state = tkNormalUid;
545     butPtr->normalBorder = NULL;
546     butPtr->activeBorder = NULL;
547     butPtr->borderWidth = 0;
548     butPtr->relief = TK_RELIEF_FLAT;
549     butPtr->highlightWidth = 0;
550     butPtr->highlightBgColorPtr = NULL;
551     butPtr->highlightColorPtr = NULL;
552     butPtr->inset = 0;
553     butPtr->fontPtr = NULL;
554     butPtr->normalFg = NULL;
555     butPtr->activeFg = NULL;
556     butPtr->disabledFg = NULL;
557     butPtr->normalTextGC = None;
558     butPtr->activeTextGC = None;
559     butPtr->gray = None;
560     butPtr->disabledGC = None;
561     butPtr->copyGC = None;
562     butPtr->widthString = NULL;
563     butPtr->heightString = NULL;
564     butPtr->width = 0;
565     butPtr->height = 0;
566     butPtr->wrapLength = 0;
567     butPtr->padX = 0;
568     butPtr->padY = 0;
569     butPtr->anchor = TK_ANCHOR_CENTER;
570     butPtr->justify = TK_JUSTIFY_CENTER;
571     butPtr->indicatorOn = 0;
572     butPtr->selectBorder = NULL;
573     butPtr->indicatorSpace = 0;
574     butPtr->indicatorDiameter = 0;
575     butPtr->selVarName = NULL;
576     butPtr->onValue = NULL;
577     butPtr->offValue = NULL;
578     butPtr->cursor = None;
579     butPtr->command = NULL;
580     butPtr->takeFocus = NULL;
581     butPtr->flags = 0;
582 
583     Tk_SetClass(new, classNames[type]);
584     Tk_CreateEventHandler(butPtr->tkwin,
585 	    ExposureMask|StructureNotifyMask|FocusChangeMask,
586 	    ButtonEventProc, (ClientData) butPtr);
587     if (ConfigureButton(interp, butPtr, argc-2, argv+2,
588 	    configFlags[type]) != TCL_OK) {
589 	Tk_DestroyWindow(butPtr->tkwin);
590 	return TCL_ERROR;
591     }
592 
593     interp->result = Tk_PathName(butPtr->tkwin);
594     return TCL_OK;
595 }
596 
597 /*
598  *--------------------------------------------------------------
599  *
600  * ButtonWidgetCmd --
601  *
602  *	This procedure is invoked to process the Tcl command
603  *	that corresponds to a widget managed by this module.
604  *	See the user documentation for details on what it does.
605  *
606  * Results:
607  *	A standard Tcl result.
608  *
609  * Side effects:
610  *	See the user documentation.
611  *
612  *--------------------------------------------------------------
613  */
614 
615 static int
ButtonWidgetCmd(clientData,interp,argc,argv)616 ButtonWidgetCmd(clientData, interp, argc, argv)
617     ClientData clientData;	/* Information about button widget. */
618     Tcl_Interp *interp;		/* Current interpreter. */
619     int argc;			/* Number of arguments. */
620     char **argv;		/* Argument strings. */
621 {
622     register Button *butPtr = (Button *) clientData;
623     int result = TCL_OK;
624     size_t length;
625     int c;
626 
627     if (argc < 2) {
628 	sprintf(interp->result,
629 		"wrong # args: should be \"%.50s option ?arg arg ...?\"",
630 		argv[0]);
631 	return TCL_ERROR;
632     }
633     Tcl_Preserve((ClientData) butPtr);
634     c = argv[1][0];
635     length = strlen(argv[1]);
636     if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
637 	    && (length >= 2)) {
638 	if (argc != 3) {
639 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
640 		    argv[0], " cget option\"",
641 		    (char *) NULL);
642 	    goto error;
643 	}
644 	result = Tk_ConfigureValue(interp, butPtr->tkwin, configSpecs,
645 		(char *) butPtr, argv[2], configFlags[butPtr->type]);
646     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
647 	    && (length >= 2)) {
648 	if (argc == 2) {
649 	    result = Tk_ConfigureInfo(interp, butPtr->tkwin, configSpecs,
650 		    (char *) butPtr, (char *) NULL, configFlags[butPtr->type]);
651 	} else if (argc == 3) {
652 	    result = Tk_ConfigureInfo(interp, butPtr->tkwin, configSpecs,
653 		    (char *) butPtr, argv[2],
654 		    configFlags[butPtr->type]);
655 	} else {
656 	    result = ConfigureButton(interp, butPtr, argc-2, argv+2,
657 		    configFlags[butPtr->type] | TK_CONFIG_ARGV_ONLY);
658 	}
659     } else if ((c == 'd') && (strncmp(argv[1], "deselect", length) == 0)
660 	    && (butPtr->type >= TYPE_CHECK_BUTTON)) {
661 	if (argc > 2) {
662 	    sprintf(interp->result,
663 		    "wrong # args: should be \"%.50s deselect\"",
664 		    argv[0]);
665 	    goto error;
666 	}
667 	if (butPtr->type == TYPE_CHECK_BUTTON) {
668 	    if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->offValue,
669 		    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
670 		result = TCL_ERROR;
671 	    }
672 	} else if (butPtr->flags & SELECTED) {
673 	    if (Tcl_SetVar(interp, butPtr->selVarName, "",
674 		    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
675 		result = TCL_ERROR;
676 	    };
677 	}
678     } else if ((c == 'f') && (strncmp(argv[1], "flash", length) == 0)
679 	    && (butPtr->type != TYPE_LABEL)) {
680 	int i;
681 
682 	if (argc > 2) {
683 	    sprintf(interp->result,
684 		    "wrong # args: should be \"%.50s flash\"",
685 		    argv[0]);
686 	    goto error;
687 	}
688 	if (butPtr->state != tkDisabledUid) {
689 	    for (i = 0; i < 4; i++) {
690 		butPtr->state = (butPtr->state == tkNormalUid)
691 			? tkActiveUid : tkNormalUid;
692 		Tk_SetBackgroundFromBorder(butPtr->tkwin,
693 			(butPtr->state == tkActiveUid) ? butPtr->activeBorder
694 			: butPtr->normalBorder);
695 		DisplayButton((ClientData) butPtr);
696 
697 		/*
698 		 * Special note: must cancel any existing idle handler
699 		 * for DisplayButton;  it's no longer needed, and DisplayButton
700 		 * cleared the REDRAW_PENDING flag.
701 		 */
702 
703 		Tcl_CancelIdleCall(DisplayButton, (ClientData) butPtr);
704 		XFlush(butPtr->display);
705 		Tcl_Sleep(50);
706 	    }
707 	}
708     } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
709 	    && (butPtr->type > TYPE_LABEL)) {
710 	if (argc > 2) {
711 	    sprintf(interp->result,
712 		    "wrong # args: should be \"%.50s invoke\"",
713 		    argv[0]);
714 	    goto error;
715 	}
716 	if (butPtr->state != tkDisabledUid) {
717 	    result = InvokeButton(butPtr);
718 	}
719     } else if ((c == 's') && (strncmp(argv[1], "select", length) == 0)
720 	    && (butPtr->type >= TYPE_CHECK_BUTTON)) {
721 	if (argc > 2) {
722 	    sprintf(interp->result,
723 		    "wrong # args: should be \"%.50s select\"",
724 		    argv[0]);
725 	    goto error;
726 	}
727 	if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->onValue,
728 		TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
729 	    result = TCL_ERROR;
730 	}
731     } else if ((c == 't') && (strncmp(argv[1], "toggle", length) == 0)
732 	    && (length >= 2) && (butPtr->type == TYPE_CHECK_BUTTON)) {
733 	if (argc > 2) {
734 	    sprintf(interp->result,
735 		    "wrong # args: should be \"%.50s toggle\"",
736 		    argv[0]);
737 	    goto error;
738 	}
739 	if (butPtr->flags & SELECTED) {
740 	    if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->offValue,
741 		    TCL_GLOBAL_ONLY) == NULL) {
742 		result = TCL_ERROR;
743 	    }
744 	} else {
745 	    if (Tcl_SetVar(interp, butPtr->selVarName, butPtr->onValue,
746 		    TCL_GLOBAL_ONLY) == NULL) {
747 		result = TCL_ERROR;
748 	    }
749 	}
750     } else {
751 	sprintf(interp->result,
752 		"bad option \"%.50s\": must be %s", argv[1],
753 		optionStrings[butPtr->type]);
754 	goto error;
755     }
756     Tcl_Release((ClientData) butPtr);
757     return result;
758 
759     error:
760     Tcl_Release((ClientData) butPtr);
761     return TCL_ERROR;
762 }
763 
764 /*
765  *----------------------------------------------------------------------
766  *
767  * DestroyButton --
768  *
769  *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
770  *	to clean up the internal structure of a button at a safe time
771  *	(when no-one is using it anymore).
772  *
773  * Results:
774  *	None.
775  *
776  * Side effects:
777  *	Everything associated with the widget is freed up.
778  *
779  *----------------------------------------------------------------------
780  */
781 
782 static void
DestroyButton(butPtr)783 DestroyButton(butPtr)
784     Button *butPtr;		/* Info about button widget. */
785 {
786     /*
787      * Free up all the stuff that requires special handling, then
788      * let Tk_FreeOptions handle all the standard option-related
789      * stuff.
790      */
791 
792     if (butPtr->textVarName != NULL) {
793 	Tcl_UntraceVar(butPtr->interp, butPtr->textVarName,
794 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
795 		ButtonTextVarProc, (ClientData) butPtr);
796     }
797     if (butPtr->image != NULL) {
798 	Tk_FreeImage(butPtr->image);
799     }
800     if (butPtr->selectImage != NULL) {
801 	Tk_FreeImage(butPtr->selectImage);
802     }
803     if (butPtr->normalTextGC != None) {
804 	Tk_FreeGC(butPtr->display, butPtr->normalTextGC);
805     }
806     if (butPtr->activeTextGC != None) {
807 	Tk_FreeGC(butPtr->display, butPtr->activeTextGC);
808     }
809     if (butPtr->gray != None) {
810 	Tk_FreeBitmap(butPtr->display, butPtr->gray);
811     }
812     if (butPtr->disabledGC != None) {
813 	Tk_FreeGC(butPtr->display, butPtr->disabledGC);
814     }
815     if (butPtr->copyGC != None) {
816 	Tk_FreeGC(butPtr->display, butPtr->copyGC);
817     }
818     if (butPtr->selVarName != NULL) {
819 	Tcl_UntraceVar(butPtr->interp, butPtr->selVarName,
820 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
821 		ButtonVarProc, (ClientData) butPtr);
822     }
823     Tk_FreeOptions(configSpecs, (char *) butPtr, butPtr->display,
824 	    configFlags[butPtr->type]);
825     Tcl_EventuallyFree((ClientData)butPtr, TCL_DYNAMIC);
826 }
827 
828 /*
829  *----------------------------------------------------------------------
830  *
831  * ConfigureButton --
832  *
833  *	This procedure is called to process an argv/argc list, plus
834  *	the Tk option database, in order to configure (or
835  *	reconfigure) a button widget.
836  *
837  * Results:
838  *	The return value is a standard Tcl result.  If TCL_ERROR is
839  *	returned, then interp->result contains an error message.
840  *
841  * Side effects:
842  *	Configuration information, such as text string, colors, font,
843  *	etc. get set for butPtr;  old resources get freed, if there
844  *	were any.  The button is redisplayed.
845  *
846  *----------------------------------------------------------------------
847  */
848 
849 static int
ConfigureButton(interp,butPtr,argc,argv,flags)850 ConfigureButton(interp, butPtr, argc, argv, flags)
851     Tcl_Interp *interp;		/* Used for error reporting. */
852     register Button *butPtr;	/* Information about widget;  may or may
853 				 * not already have values for some fields. */
854     int argc;			/* Number of valid entries in argv. */
855     char **argv;		/* Arguments. */
856     int flags;			/* Flags to pass to Tk_ConfigureWidget. */
857 {
858     XGCValues gcValues;
859     GC newGC;
860     unsigned long mask;
861     Tk_Image image;
862 
863     /*
864      * Eliminate any existing trace on variables monitored by the button.
865      */
866 
867     if (butPtr->textVarName != NULL) {
868 	Tcl_UntraceVar(interp, butPtr->textVarName,
869 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
870 		ButtonTextVarProc, (ClientData) butPtr);
871     }
872     if (butPtr->selVarName != NULL) {
873 	Tcl_UntraceVar(interp, butPtr->selVarName,
874 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
875 		ButtonVarProc, (ClientData) butPtr);
876     }
877 
878     if (Tk_ConfigureWidget(interp, butPtr->tkwin, configSpecs,
879 	    argc, argv, (char *) butPtr, flags) != TCL_OK) {
880 	return TCL_ERROR;
881     }
882 
883     /*
884      * A few options need special processing, such as setting the
885      * background from a 3-D border, or filling in complicated
886      * defaults that couldn't be specified to Tk_ConfigureWidget.
887      */
888 
889     if ((butPtr->state == tkActiveUid) && !Tk_StrictMotif(butPtr->tkwin)) {
890 	Tk_SetBackgroundFromBorder(butPtr->tkwin, butPtr->activeBorder);
891     } else {
892 	Tk_SetBackgroundFromBorder(butPtr->tkwin, butPtr->normalBorder);
893 	if ((butPtr->state != tkNormalUid) && (butPtr->state != tkActiveUid)
894 		&& (butPtr->state != tkDisabledUid)) {
895 	    Tcl_AppendResult(interp, "bad state value \"", butPtr->state,
896 		    "\": must be normal, active, or disabled", (char *) NULL);
897 	    butPtr->state = tkNormalUid;
898 	    return TCL_ERROR;
899 	}
900     }
901 
902     if (butPtr->highlightWidth < 0) {
903 	butPtr->highlightWidth = 0;
904     }
905 
906     gcValues.font = butPtr->fontPtr->fid;
907     gcValues.foreground = butPtr->normalFg->pixel;
908     gcValues.background = Tk_3DBorderColor(butPtr->normalBorder)->pixel;
909 
910     /*
911      * Note: GraphicsExpose events are disabled in normalTextGC because it's
912      * used to copy stuff from an off-screen pixmap onto the screen (we know
913      * that there's no problem with obscured areas).
914      */
915 
916     gcValues.graphics_exposures = False;
917     newGC = Tk_GetGC(butPtr->tkwin,
918 	    GCForeground|GCBackground|GCFont|GCGraphicsExposures,
919 	    &gcValues);
920     if (butPtr->normalTextGC != None) {
921 	Tk_FreeGC(butPtr->display, butPtr->normalTextGC);
922     }
923     butPtr->normalTextGC = newGC;
924 
925     if (butPtr->activeFg != NULL) {
926 	gcValues.font = butPtr->fontPtr->fid;
927 	gcValues.foreground = butPtr->activeFg->pixel;
928 	gcValues.background = Tk_3DBorderColor(butPtr->activeBorder)->pixel;
929 	newGC = Tk_GetGC(butPtr->tkwin,
930 		GCForeground|GCBackground|GCFont, &gcValues);
931 	if (butPtr->activeTextGC != None) {
932 	    Tk_FreeGC(butPtr->display, butPtr->activeTextGC);
933 	}
934 	butPtr->activeTextGC = newGC;
935     }
936 
937     if (butPtr->type != TYPE_LABEL) {
938 	gcValues.font = butPtr->fontPtr-> fid;
939 	gcValues.background = Tk_3DBorderColor(butPtr->normalBorder)->pixel;
940 	if ((butPtr->disabledFg != NULL) && (butPtr->imageString == NULL)) {
941 	    gcValues.foreground = butPtr->disabledFg->pixel;
942 	    mask = GCForeground|GCBackground|GCFont;
943 	} else {
944 	    gcValues.foreground = gcValues.background;
945 	    if (butPtr->gray == None) {
946 		butPtr->gray = Tk_GetBitmap(interp, butPtr->tkwin,
947 			Tk_GetUid("gray50"));
948 		if (butPtr->gray == None) {
949 		    return TCL_ERROR;
950 		}
951 	    }
952 	    gcValues.fill_style = FillStippled;
953 	    gcValues.stipple = butPtr->gray;
954 	    mask = GCForeground|GCFillStyle|GCStipple;
955 	}
956 	newGC = Tk_GetGC(butPtr->tkwin, mask, &gcValues);
957 	if (butPtr->disabledGC != None) {
958 	    Tk_FreeGC(butPtr->display, butPtr->disabledGC);
959 	}
960 	butPtr->disabledGC = newGC;
961     }
962 
963     if (butPtr->copyGC == None) {
964 	butPtr->copyGC = Tk_GetGC(butPtr->tkwin, 0, &gcValues);
965     }
966 
967     if (butPtr->padX < 0) {
968 	butPtr->padX = 0;
969     }
970     if (butPtr->padY < 0) {
971 	butPtr->padY = 0;
972     }
973 
974     if (butPtr->type >= TYPE_CHECK_BUTTON) {
975 	char *value;
976 
977 	if (butPtr->selVarName == NULL) {
978 	    butPtr->selVarName = (char *) ckalloc((unsigned)
979 		    (strlen(Tk_Name(butPtr->tkwin)) + 1));
980 	    strcpy(butPtr->selVarName, Tk_Name(butPtr->tkwin));
981 	}
982 
983 	/*
984 	 * Select the button if the associated variable has the
985 	 * appropriate value, initialize the variable if it doesn't
986 	 * exist, then set a trace on the variable to monitor future
987 	 * changes to its value.
988 	 */
989 
990 	value = Tcl_GetVar(interp, butPtr->selVarName, TCL_GLOBAL_ONLY);
991 	butPtr->flags &= ~SELECTED;
992 	if (value != NULL) {
993 	    if (strcmp(value, butPtr->onValue) == 0) {
994 		butPtr->flags |= SELECTED;
995 	    }
996 	} else {
997 	    if (Tcl_SetVar(interp, butPtr->selVarName,
998 		    (butPtr->type == TYPE_CHECK_BUTTON) ? butPtr->offValue : "",
999 		    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
1000 		return TCL_ERROR;
1001 	    }
1002 	}
1003 	Tcl_TraceVar(interp, butPtr->selVarName,
1004 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1005 		ButtonVarProc, (ClientData) butPtr);
1006     }
1007 
1008     /*
1009      * Get the images for the widget, if there are any.  Allocate the
1010      * new images before freeing the old ones, so that the reference
1011      * counts don't go to zero and cause image data to be discarded.
1012      */
1013 
1014     if (butPtr->imageString != NULL) {
1015 	image = Tk_GetImage(butPtr->interp, butPtr->tkwin,
1016 		butPtr->imageString, ButtonImageProc, (ClientData) butPtr);
1017 	if (image == NULL) {
1018 	    return TCL_ERROR;
1019 	}
1020     } else {
1021 	image = NULL;
1022     }
1023     if (butPtr->image != NULL) {
1024 	Tk_FreeImage(butPtr->image);
1025     }
1026     butPtr->image = image;
1027     if (butPtr->selectImageString != NULL) {
1028 	image = Tk_GetImage(butPtr->interp, butPtr->tkwin,
1029 		butPtr->selectImageString, ButtonSelectImageProc,
1030 		(ClientData) butPtr);
1031 	if (image == NULL) {
1032 	    return TCL_ERROR;
1033 	}
1034     } else {
1035 	image = NULL;
1036     }
1037     if (butPtr->selectImage != NULL) {
1038 	Tk_FreeImage(butPtr->selectImage);
1039     }
1040     butPtr->selectImage = image;
1041 
1042     if ((butPtr->image == NULL) && (butPtr->bitmap == None)
1043 	    && (butPtr->textVarName != NULL)) {
1044 	/*
1045 	 * The button must display the value of a variable: set up a trace
1046 	 * on the variable's value, create the variable if it doesn't
1047 	 * exist, and fetch its current value.
1048 	 */
1049 
1050 	char *value;
1051 
1052 	value = Tcl_GetVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY);
1053 	if (value == NULL) {
1054 	    if (Tcl_SetVar(interp, butPtr->textVarName, butPtr->text,
1055 		    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
1056 		return TCL_ERROR;
1057 	    }
1058 	} else {
1059 	    if (butPtr->text != NULL) {
1060 		ckfree(butPtr->text);
1061 	    }
1062 	    butPtr->text = (char *) ckalloc((unsigned) (strlen(value) + 1));
1063 	    strcpy(butPtr->text, value);
1064 	}
1065 	Tcl_TraceVar(interp, butPtr->textVarName,
1066 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1067 		ButtonTextVarProc, (ClientData) butPtr);
1068     }
1069 
1070     if ((butPtr->bitmap != None) || (butPtr->image != NULL)) {
1071 	if (Tk_GetPixels(interp, butPtr->tkwin, butPtr->widthString,
1072 		&butPtr->width) != TCL_OK) {
1073 	    widthError:
1074 	    Tcl_AddErrorInfo(interp, "\n    (processing -width option)");
1075 	    return TCL_ERROR;
1076 	}
1077 	if (Tk_GetPixels(interp, butPtr->tkwin, butPtr->heightString,
1078 		&butPtr->height) != TCL_OK) {
1079 	    heightError:
1080 	    Tcl_AddErrorInfo(interp, "\n    (processing -height option)");
1081 	    return TCL_ERROR;
1082 	}
1083     } else {
1084 	if (Tcl_GetInt(interp, butPtr->widthString, &butPtr->width)
1085 		!= TCL_OK) {
1086 	    goto widthError;
1087 	}
1088 	if (Tcl_GetInt(interp, butPtr->heightString, &butPtr->height)
1089 		!= TCL_OK) {
1090 	    goto heightError;
1091 	}
1092     }
1093     ComputeButtonGeometry(butPtr);
1094 
1095     /*
1096      * Lastly, arrange for the button to be redisplayed.
1097      */
1098 
1099     if (Tk_IsMapped(butPtr->tkwin) && !(butPtr->flags & REDRAW_PENDING)) {
1100 	Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr);
1101 	butPtr->flags |= REDRAW_PENDING;
1102     }
1103 
1104     return TCL_OK;
1105 }
1106 
1107 /*
1108  *----------------------------------------------------------------------
1109  *
1110  * DisplayButton --
1111  *
1112  *	This procedure is invoked to display a button widget.  It is
1113  *	normally invoked as an idle handler.
1114  *
1115  * Results:
1116  *	None.
1117  *
1118  * Side effects:
1119  *	Commands are output to X to display the button in its
1120  *	current mode.  The REDRAW_PENDING flag is cleared.
1121  *
1122  *----------------------------------------------------------------------
1123  */
1124 
1125 static void
DisplayButton(clientData)1126 DisplayButton(clientData)
1127     ClientData clientData;	/* Information about widget. */
1128 {
1129     register Button *butPtr = (Button *) clientData;
1130     GC gc;
1131     Tk_3DBorder border;
1132     Pixmap pixmap;
1133     int x = 0;			/* Initialization only needed to stop
1134 				 * compiler warning. */
1135     int y, relief;
1136     register Tk_Window tkwin = butPtr->tkwin;
1137     int width, height;
1138     int offset;			/* 0 means this is a label widget.  1 means
1139 				 * it is a flavor of button, so we offset
1140 				 * the text to make the button appear to
1141 				 * move up and down as the relief changes. */
1142 
1143     butPtr->flags &= ~REDRAW_PENDING;
1144     if ((butPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
1145 	return;
1146     }
1147 
1148     border = butPtr->normalBorder;
1149     if ((butPtr->state == tkDisabledUid) && (butPtr->disabledFg != NULL)) {
1150 	gc = butPtr->disabledGC;
1151     } else if ((butPtr->state == tkActiveUid)
1152 	    && !Tk_StrictMotif(butPtr->tkwin)) {
1153 	gc = butPtr->activeTextGC;
1154 	border = butPtr->activeBorder;
1155     } else {
1156 	gc = butPtr->normalTextGC;
1157     }
1158     if ((butPtr->flags & SELECTED) && (butPtr->state != tkActiveUid)
1159 	    && (butPtr->selectBorder != NULL) && !butPtr->indicatorOn) {
1160 	border = butPtr->selectBorder;
1161     }
1162 
1163     /*
1164      * Override the relief specified for the button if this is a
1165      * checkbutton or radiobutton and there's no indicator.
1166      */
1167 
1168     relief = butPtr->relief;
1169     if ((butPtr->type >= TYPE_CHECK_BUTTON) && !butPtr->indicatorOn) {
1170 	relief = (butPtr->flags & SELECTED) ? TK_RELIEF_SUNKEN
1171 		: TK_RELIEF_RAISED;
1172     }
1173 
1174     offset = (butPtr->type == TYPE_BUTTON) && !Tk_StrictMotif(butPtr->tkwin);
1175 
1176     /*
1177      * In order to avoid screen flashes, this procedure redraws
1178      * the button in a pixmap, then copies the pixmap to the
1179      * screen in a single operation.  This means that there's no
1180      * point in time where the on-sreen image has been cleared.
1181      */
1182 
1183     pixmap = Tk_GetPixmap(butPtr->display, Tk_WindowId(tkwin),
1184 	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
1185     Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
1186 	    Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
1187 
1188     /*
1189      * Display image or bitmap or text for button.
1190      */
1191 
1192     if (butPtr->image != None) {
1193 	Tk_SizeOfImage(butPtr->image, &width, &height);
1194 
1195 	imageOrBitmap:
1196 	switch (butPtr->anchor) {
1197 	    case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
1198 		x = butPtr->inset + butPtr->indicatorSpace + offset;
1199 		break;
1200 	    case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
1201 		x = ((int) (Tk_Width(tkwin) + butPtr->indicatorSpace
1202 			- width))/2;
1203 		break;
1204 	    default:
1205 		x = Tk_Width(tkwin) - butPtr->inset - width - offset;
1206 		break;
1207 	}
1208 	switch (butPtr->anchor) {
1209 	    case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
1210 		y = butPtr->inset + offset;
1211 		break;
1212 	    case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
1213 		y = ((int) (Tk_Height(tkwin) - height))/2;
1214 		break;
1215 	    default:
1216 		y = Tk_Height(tkwin) - butPtr->inset - height - offset;
1217 		break;
1218 	}
1219 	if (relief == TK_RELIEF_RAISED) {
1220 	    x -= offset;
1221 	    y -= offset;
1222 	} else if (relief == TK_RELIEF_SUNKEN) {
1223 	    x += offset;
1224 	    y += offset;
1225 	}
1226 	if (butPtr->image != NULL) {
1227 	    if ((butPtr->selectImage != NULL) && (butPtr->flags & SELECTED)) {
1228 		Tk_RedrawImage(butPtr->selectImage, 0, 0, width, height, pixmap,
1229 			x, y);
1230 	    } else {
1231 		Tk_RedrawImage(butPtr->image, 0, 0, width, height, pixmap,
1232 			x, y);
1233 	    }
1234 	} else {
1235 	    XSetClipOrigin(butPtr->display, gc, x, y);
1236 	    XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 0, 0,
1237 		    (unsigned int) width, (unsigned int) height, x, y, 1);
1238 	    XSetClipOrigin(butPtr->display, gc, 0, 0);
1239 	}
1240 	y += height/2;
1241     } else if (butPtr->bitmap != None) {
1242 	Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
1243 	goto imageOrBitmap;
1244     } else {
1245 	switch (butPtr->anchor) {
1246 	    case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
1247 		x = butPtr->inset + butPtr->padX + butPtr->indicatorSpace
1248 			+ offset;
1249 		break;
1250 	    case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
1251 		x = ((int) (Tk_Width(tkwin) + butPtr->indicatorSpace
1252 			- butPtr->textWidth))/2;
1253 		break;
1254 	    default:
1255 		x = Tk_Width(tkwin) - butPtr->inset - butPtr->padX
1256 			- butPtr->textWidth - offset;
1257 		break;
1258 	}
1259 	switch (butPtr->anchor) {
1260 	    case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
1261 		y = butPtr->inset + butPtr->padY + offset;
1262 		break;
1263 	    case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
1264 		y = ((int) (Tk_Height(tkwin) - butPtr->textHeight))/2;
1265 		break;
1266 	    default:
1267 		y = Tk_Height(tkwin) - butPtr->inset - butPtr->padY
1268 			- butPtr->textHeight - offset;
1269 		break;
1270 	}
1271 	if (relief == TK_RELIEF_RAISED) {
1272 	    x -= offset;
1273 	    y -= offset;
1274 	} else if (relief == TK_RELIEF_SUNKEN) {
1275 	    x += offset;
1276 	    y += offset;
1277 	}
1278 	TkDisplayText(butPtr->display, pixmap, butPtr->fontPtr,
1279 		butPtr->text, butPtr->numChars, x, y, butPtr->textWidth,
1280 		butPtr->justify, butPtr->underline, gc);
1281 	y += butPtr->textHeight/2;
1282     }
1283 
1284     /*
1285      * Draw the indicator for check buttons and radio buttons.  At this
1286      * point x and y refer to the top-left corner of the text or image
1287      * or bitmap.
1288      */
1289 
1290     if ((butPtr->type == TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
1291 	int dim;
1292 
1293 	dim = butPtr->indicatorDiameter;
1294 	x -= butPtr->indicatorSpace;
1295 	y -= dim/2;
1296 	if (dim > 2*butPtr->borderWidth) {
1297 	    Tk_Draw3DRectangle(tkwin, pixmap, border, x, y, dim, dim,
1298 		    butPtr->borderWidth,
1299 		    (butPtr->flags & SELECTED) ? TK_RELIEF_SUNKEN :
1300 		    TK_RELIEF_RAISED);
1301 	    x += butPtr->borderWidth;
1302 	    y += butPtr->borderWidth;
1303 	    dim -= 2*butPtr->borderWidth;
1304 	    if (butPtr->flags & SELECTED) {
1305 		GC gc;
1306 
1307 		gc = Tk_3DBorderGC(tkwin,(butPtr->selectBorder != NULL)
1308 			? butPtr->selectBorder : butPtr->normalBorder,
1309 			TK_3D_FLAT_GC);
1310 		XFillRectangle(butPtr->display, pixmap, gc, x, y,
1311 			(unsigned int) dim, (unsigned int) dim);
1312 	    } else {
1313 		Tk_Fill3DRectangle(tkwin, pixmap, butPtr->normalBorder, x, y,
1314 			dim, dim, butPtr->borderWidth, TK_RELIEF_FLAT);
1315 	    }
1316 	}
1317     } else if ((butPtr->type == TYPE_RADIO_BUTTON) && butPtr->indicatorOn) {
1318 	XPoint points[4];
1319 	int radius;
1320 
1321 	radius = butPtr->indicatorDiameter/2;
1322 	points[0].x = x - butPtr->indicatorSpace;
1323 	points[0].y = y;
1324 	points[1].x = points[0].x + radius;
1325 	points[1].y = points[0].y + radius;
1326 	points[2].x = points[1].x + radius;
1327 	points[2].y = points[0].y;
1328 	points[3].x = points[1].x;
1329 	points[3].y = points[0].y - radius;
1330 	if (butPtr->flags & SELECTED) {
1331 	    GC gc;
1332 
1333 	    gc = Tk_3DBorderGC(tkwin, (butPtr->selectBorder != NULL)
1334 		    ? butPtr->selectBorder : butPtr->normalBorder,
1335 		    TK_3D_FLAT_GC);
1336 	    XFillPolygon(butPtr->display, pixmap, gc, points, 4, Convex,
1337 		    CoordModeOrigin);
1338 	} else {
1339 	    Tk_Fill3DPolygon(tkwin, pixmap, butPtr->normalBorder, points,
1340 		    4, butPtr->borderWidth, TK_RELIEF_FLAT);
1341 	}
1342 	Tk_Draw3DPolygon(tkwin, pixmap, border, points, 4, butPtr->borderWidth,
1343 		(butPtr->flags & SELECTED) ? TK_RELIEF_SUNKEN :
1344 		TK_RELIEF_RAISED);
1345     }
1346 
1347     /*
1348      * If the button is disabled with a stipple rather than a special
1349      * foreground color, generate the stippled effect.  If the widget
1350      * is selected and we use a different background color when selected,
1351      * must temporarily modify the GC.
1352      */
1353 
1354     if ((butPtr->state == tkDisabledUid)
1355 	    && ((butPtr->disabledFg == NULL) || (butPtr->image != NULL))) {
1356 	if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn
1357 		&& (butPtr->selectBorder != NULL)) {
1358 	    XSetForeground(butPtr->display, butPtr->disabledGC,
1359 		    Tk_3DBorderColor(butPtr->selectBorder)->pixel);
1360 	}
1361 	XFillRectangle(butPtr->display, pixmap, butPtr->disabledGC,
1362 		butPtr->inset, butPtr->inset,
1363 		(unsigned) (Tk_Width(tkwin) - 2*butPtr->inset),
1364 		(unsigned) (Tk_Height(tkwin) - 2*butPtr->inset));
1365 	if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn
1366 		&& (butPtr->selectBorder != NULL)) {
1367 	    XSetForeground(butPtr->display, butPtr->disabledGC,
1368 		    Tk_3DBorderColor(butPtr->normalBorder)->pixel);
1369 	}
1370     }
1371 
1372     /*
1373      * Draw the border and traversal highlight last.  This way, if the
1374      * button's contents overflow they'll be covered up by the border.
1375      */
1376 
1377     if (relief != TK_RELIEF_FLAT) {
1378 	Tk_Draw3DRectangle(tkwin, pixmap, border,
1379 		butPtr->highlightWidth, butPtr->highlightWidth,
1380 		Tk_Width(tkwin) - 2*butPtr->highlightWidth,
1381 		Tk_Height(tkwin) - 2*butPtr->highlightWidth,
1382 		butPtr->borderWidth, relief);
1383     }
1384     if (butPtr->highlightWidth != 0) {
1385 	GC gc;
1386 
1387 	if (butPtr->flags & GOT_FOCUS) {
1388 	    gc = Tk_GCForColor(butPtr->highlightColorPtr, pixmap);
1389 	} else {
1390 	    gc = Tk_GCForColor(butPtr->highlightBgColorPtr, pixmap);
1391 	}
1392 	Tk_DrawFocusHighlight(tkwin, gc, butPtr->highlightWidth, pixmap);
1393     }
1394 
1395     /*
1396      * Copy the information from the off-screen pixmap onto the screen,
1397      * then delete the pixmap.
1398      */
1399 
1400     XCopyArea(butPtr->display, pixmap, Tk_WindowId(tkwin),
1401 	    butPtr->copyGC, 0, 0, (unsigned) Tk_Width(tkwin),
1402 	    (unsigned) Tk_Height(tkwin), 0, 0);
1403     Tk_FreePixmap(butPtr->display, pixmap);
1404 }
1405 
1406 /*
1407  *--------------------------------------------------------------
1408  *
1409  * ButtonEventProc --
1410  *
1411  *	This procedure is invoked by the Tk dispatcher for various
1412  *	events on buttons.
1413  *
1414  * Results:
1415  *	None.
1416  *
1417  * Side effects:
1418  *	When the window gets deleted, internal structures get
1419  *	cleaned up.  When it gets exposed, it is redisplayed.
1420  *
1421  *--------------------------------------------------------------
1422  */
1423 
1424 static void
ButtonEventProc(clientData,eventPtr)1425 ButtonEventProc(clientData, eventPtr)
1426     ClientData clientData;	/* Information about window. */
1427     XEvent *eventPtr;		/* Information about event. */
1428 {
1429     Button *butPtr = (Button *) clientData;
1430     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
1431 	goto redraw;
1432     } else if (eventPtr->type == ConfigureNotify) {
1433 	/*
1434 	 * Must redraw after size changes, since layout could have changed
1435 	 * and borders will need to be redrawn.
1436 	 */
1437 
1438 	goto redraw;
1439     } else if (eventPtr->type == DestroyNotify) {
1440 	if (butPtr->tkwin != NULL) {
1441 	    butPtr->tkwin = NULL;
1442 	    Tcl_DeleteCommand(butPtr->interp,
1443 		    Tcl_GetCommandName(butPtr->interp, butPtr->widgetCmd));
1444 	}
1445 	if (butPtr->flags & REDRAW_PENDING) {
1446 	    Tcl_CancelIdleCall(DisplayButton, (ClientData) butPtr);
1447 	}
1448 	DestroyButton(butPtr);
1449     } else if (eventPtr->type == FocusIn) {
1450 	if (eventPtr->xfocus.detail != NotifyInferior) {
1451 	    butPtr->flags |= GOT_FOCUS;
1452 	    if (butPtr->highlightWidth > 0) {
1453 		goto redraw;
1454 	    }
1455 	}
1456     } else if (eventPtr->type == FocusOut) {
1457 	if (eventPtr->xfocus.detail != NotifyInferior) {
1458 	    butPtr->flags &= ~GOT_FOCUS;
1459 	    if (butPtr->highlightWidth > 0) {
1460 		goto redraw;
1461 	    }
1462 	}
1463     }
1464     return;
1465 
1466     redraw:
1467     if ((butPtr->tkwin != NULL) && !(butPtr->flags & REDRAW_PENDING)) {
1468 	Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr);
1469 	butPtr->flags |= REDRAW_PENDING;
1470     }
1471 }
1472 
1473 /*
1474  *----------------------------------------------------------------------
1475  *
1476  * ButtonCmdDeletedProc --
1477  *
1478  *	This procedure is invoked when a widget command is deleted.  If
1479  *	the widget isn't already in the process of being destroyed,
1480  *	this command destroys it.
1481  *
1482  * Results:
1483  *	None.
1484  *
1485  * Side effects:
1486  *	The widget is destroyed.
1487  *
1488  *----------------------------------------------------------------------
1489  */
1490 
1491 static void
ButtonCmdDeletedProc(clientData)1492 ButtonCmdDeletedProc(clientData)
1493     ClientData clientData;	/* Pointer to widget record for widget. */
1494 {
1495     Button *butPtr = (Button *) clientData;
1496     Tk_Window tkwin = butPtr->tkwin;
1497 
1498     /*
1499      * This procedure could be invoked either because the window was
1500      * destroyed and the command was then deleted (in which case tkwin
1501      * is NULL) or because the command was deleted, and then this procedure
1502      * destroys the widget.
1503      */
1504 
1505     if (tkwin != NULL) {
1506 	butPtr->tkwin = NULL;
1507 	Tk_DestroyWindow(tkwin);
1508     }
1509 }
1510 
1511 /*
1512  *----------------------------------------------------------------------
1513  *
1514  * ComputeButtonGeometry --
1515  *
1516  *	After changes in a button's text or bitmap, this procedure
1517  *	recomputes the button's geometry and passes this information
1518  *	along to the geometry manager for the window.
1519  *
1520  * Results:
1521  *	None.
1522  *
1523  * Side effects:
1524  *	The button's window may change size.
1525  *
1526  *----------------------------------------------------------------------
1527  */
1528 
1529 static void
ComputeButtonGeometry(butPtr)1530 ComputeButtonGeometry(butPtr)
1531     register Button *butPtr;	/* Button whose geometry may have changed. */
1532 {
1533     int width, height;
1534 
1535     if (butPtr->highlightWidth < 0) {
1536 	butPtr->highlightWidth = 0;
1537     }
1538     butPtr->inset = butPtr->highlightWidth + butPtr->borderWidth;
1539     butPtr->indicatorSpace = 0;
1540     if (butPtr->image != NULL) {
1541 	Tk_SizeOfImage(butPtr->image, &width, &height);
1542 	imageOrBitmap:
1543 	if (butPtr->width > 0) {
1544 	    width = butPtr->width;
1545 	}
1546 	if (butPtr->height > 0) {
1547 	    height = butPtr->height;
1548 	}
1549 	if ((butPtr->type >= TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
1550 	    butPtr->indicatorSpace = height;
1551 	    if (butPtr->type == TYPE_CHECK_BUTTON) {
1552 		butPtr->indicatorDiameter = (65*height)/100;
1553 	    } else {
1554 		butPtr->indicatorDiameter = (75*height)/100;
1555 	    }
1556 	}
1557     } else if (butPtr->bitmap != None) {
1558 	Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
1559 	goto imageOrBitmap;
1560     } else {
1561 	butPtr->numChars = strlen(butPtr->text);
1562 	TkComputeTextGeometry(butPtr->fontPtr, butPtr->text,
1563 		butPtr->numChars, butPtr->wrapLength, &butPtr->textWidth,
1564 		&butPtr->textHeight);
1565 	width = butPtr->textWidth;
1566 	height = butPtr->textHeight;
1567 	if (butPtr->width > 0) {
1568 	    width = butPtr->width * XTextWidth(butPtr->fontPtr, "0", 1);
1569 	}
1570 	if (butPtr->height > 0) {
1571 	    height = butPtr->height * (butPtr->fontPtr->ascent
1572 		    + butPtr->fontPtr->descent);
1573 	}
1574 	if ((butPtr->type >= TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
1575 	    butPtr->indicatorDiameter = butPtr->fontPtr->ascent
1576 		    + butPtr->fontPtr->descent;
1577 	    if (butPtr->type == TYPE_CHECK_BUTTON) {
1578 		butPtr->indicatorDiameter = (80*butPtr->indicatorDiameter)/100;
1579 	    }
1580 	    butPtr->indicatorSpace = butPtr->indicatorDiameter
1581 		+ XTextWidth(butPtr->fontPtr, "0", 1);
1582 	}
1583     }
1584 
1585     /*
1586      * When issuing the geometry request, add extra space for the indicator,
1587      * if any, and for the border and padding, plus two extra pixels so the
1588      * display can be offset by 1 pixel in either direction for the raised
1589      * or lowered effect.
1590      */
1591 
1592     if ((butPtr->image == NULL) && (butPtr->bitmap == None)) {
1593 	width += 2*butPtr->padX;
1594 	height += 2*butPtr->padY;
1595     }
1596     if ((butPtr->type == TYPE_BUTTON) && !Tk_StrictMotif(butPtr->tkwin)) {
1597 	width += 2;
1598 	height += 2;
1599     }
1600     Tk_GeometryRequest(butPtr->tkwin, (int) (width + butPtr->indicatorSpace
1601 	    + 2*butPtr->inset), (int) (height + 2*butPtr->inset));
1602     Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset);
1603 }
1604 
1605 /*
1606  *----------------------------------------------------------------------
1607  *
1608  * InvokeButton --
1609  *
1610  *	This procedure is called to carry out the actions associated
1611  *	with a button, such as invoking a Tcl command or setting a
1612  *	variable.  This procedure is invoked, for example, when the
1613  *	button is invoked via the mouse.
1614  *
1615  * Results:
1616  *	A standard Tcl return value.  Information is also left in
1617  *	interp->result.
1618  *
1619  * Side effects:
1620  *	Depends on the button and its associated command.
1621  *
1622  *----------------------------------------------------------------------
1623  */
1624 
1625 static int
InvokeButton(butPtr)1626 InvokeButton(butPtr)
1627     register Button *butPtr;		/* Information about button. */
1628 {
1629     if (butPtr->type == TYPE_CHECK_BUTTON) {
1630 	if (butPtr->flags & SELECTED) {
1631 	    if (Tcl_SetVar(butPtr->interp, butPtr->selVarName, butPtr->offValue,
1632 		    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
1633 		return TCL_ERROR;
1634 	    }
1635 	} else {
1636 	    if (Tcl_SetVar(butPtr->interp, butPtr->selVarName, butPtr->onValue,
1637 		    TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
1638 		return TCL_ERROR;
1639 	    }
1640 	}
1641     } else if (butPtr->type == TYPE_RADIO_BUTTON) {
1642 	if (Tcl_SetVar(butPtr->interp, butPtr->selVarName, butPtr->onValue,
1643 		TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
1644 	    return TCL_ERROR;
1645 	}
1646     }
1647     if ((butPtr->type != TYPE_LABEL) && (butPtr->command != NULL)) {
1648 	return TkCopyAndGlobalEval(butPtr->interp, butPtr->command);
1649     }
1650     return TCL_OK;
1651 }
1652 
1653 /*
1654  *--------------------------------------------------------------
1655  *
1656  * ButtonVarProc --
1657  *
1658  *	This procedure is invoked when someone changes the
1659  *	state variable associated with a radio button.  Depending
1660  *	on the new value of the button's variable, the button
1661  *	may be selected or deselected.
1662  *
1663  * Results:
1664  *	NULL is always returned.
1665  *
1666  * Side effects:
1667  *	The button may become selected or deselected.
1668  *
1669  *--------------------------------------------------------------
1670  */
1671 
1672 	/* ARGSUSED */
1673 static char *
ButtonVarProc(clientData,interp,name1,name2,flags)1674 ButtonVarProc(clientData, interp, name1, name2, flags)
1675     ClientData clientData;	/* Information about button. */
1676     Tcl_Interp *interp;		/* Interpreter containing variable. */
1677     char *name1;		/* Name of variable. */
1678     char *name2;		/* Second part of variable name. */
1679     int flags;			/* Information about what happened. */
1680 {
1681     register Button *butPtr = (Button *) clientData;
1682     char *value;
1683 
1684     /*
1685      * If the variable is being unset, then just re-establish the
1686      * trace unless the whole interpreter is going away.
1687      */
1688 
1689     if (flags & TCL_TRACE_UNSETS) {
1690 	butPtr->flags &= ~SELECTED;
1691 	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
1692 	    Tcl_TraceVar(interp, butPtr->selVarName,
1693 		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1694 		    ButtonVarProc, clientData);
1695 	}
1696 	goto redisplay;
1697     }
1698 
1699     /*
1700      * Use the value of the variable to update the selected status of
1701      * the button.
1702      */
1703 
1704     value = Tcl_GetVar(interp, butPtr->selVarName, TCL_GLOBAL_ONLY);
1705     if (value == NULL) {
1706 	value = "";
1707     }
1708     if (strcmp(value, butPtr->onValue) == 0) {
1709 	if (butPtr->flags & SELECTED) {
1710 	    return (char *) NULL;
1711 	}
1712 	butPtr->flags |= SELECTED;
1713     } else if (butPtr->flags & SELECTED) {
1714 	butPtr->flags &= ~SELECTED;
1715     } else {
1716 	return (char *) NULL;
1717     }
1718 
1719     redisplay:
1720     if ((butPtr->tkwin != NULL) && Tk_IsMapped(butPtr->tkwin)
1721 	    && !(butPtr->flags & REDRAW_PENDING)) {
1722 	Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr);
1723 	butPtr->flags |= REDRAW_PENDING;
1724     }
1725     return (char *) NULL;
1726 }
1727 
1728 /*
1729  *--------------------------------------------------------------
1730  *
1731  * ButtonTextVarProc --
1732  *
1733  *	This procedure is invoked when someone changes the variable
1734  *	whose contents are to be displayed in a button.
1735  *
1736  * Results:
1737  *	NULL is always returned.
1738  *
1739  * Side effects:
1740  *	The text displayed in the button will change to match the
1741  *	variable.
1742  *
1743  *--------------------------------------------------------------
1744  */
1745 
1746 	/* ARGSUSED */
1747 static char *
ButtonTextVarProc(clientData,interp,name1,name2,flags)1748 ButtonTextVarProc(clientData, interp, name1, name2, flags)
1749     ClientData clientData;	/* Information about button. */
1750     Tcl_Interp *interp;		/* Interpreter containing variable. */
1751     char *name1;		/* Not used. */
1752     char *name2;		/* Not used. */
1753     int flags;			/* Information about what happened. */
1754 {
1755     register Button *butPtr = (Button *) clientData;
1756     char *value;
1757 
1758     /*
1759      * If the variable is unset, then immediately recreate it unless
1760      * the whole interpreter is going away.
1761      */
1762 
1763     if (flags & TCL_TRACE_UNSETS) {
1764 	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
1765 	    Tcl_SetVar(interp, butPtr->textVarName, butPtr->text,
1766 		    TCL_GLOBAL_ONLY);
1767 	    Tcl_TraceVar(interp, butPtr->textVarName,
1768 		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1769 		    ButtonTextVarProc, clientData);
1770 	}
1771 	return (char *) NULL;
1772     }
1773 
1774     value = Tcl_GetVar(interp, butPtr->textVarName, TCL_GLOBAL_ONLY);
1775     if (value == NULL) {
1776 	value = "";
1777     }
1778     if (butPtr->text != NULL) {
1779 	ckfree(butPtr->text);
1780     }
1781     butPtr->text = (char *) ckalloc((unsigned) (strlen(value) + 1));
1782     strcpy(butPtr->text, value);
1783     ComputeButtonGeometry(butPtr);
1784 
1785     if ((butPtr->tkwin != NULL) && Tk_IsMapped(butPtr->tkwin)
1786 	    && !(butPtr->flags & REDRAW_PENDING)) {
1787 	Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr);
1788 	butPtr->flags |= REDRAW_PENDING;
1789     }
1790     return (char *) NULL;
1791 }
1792 
1793 /*
1794  *----------------------------------------------------------------------
1795  *
1796  * ButtonImageProc --
1797  *
1798  *	This procedure is invoked by the image code whenever the manager
1799  *	for an image does something that affects the size of contents
1800  *	of an image displayed in a button.
1801  *
1802  * Results:
1803  *	None.
1804  *
1805  * Side effects:
1806  *	Arranges for the button to get redisplayed.
1807  *
1808  *----------------------------------------------------------------------
1809  */
1810 
1811 static void
ButtonImageProc(clientData,x,y,width,height,imgWidth,imgHeight)1812 ButtonImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
1813     ClientData clientData;		/* Pointer to widget record. */
1814     int x, y;				/* Upper left pixel (within image)
1815 					 * that must be redisplayed. */
1816     int width, height;			/* Dimensions of area to redisplay
1817 					 * (may be <= 0). */
1818     int imgWidth, imgHeight;		/* New dimensions of image. */
1819 {
1820     register Button *butPtr = (Button *) clientData;
1821 
1822     if (butPtr->tkwin != NULL) {
1823 	ComputeButtonGeometry(butPtr);
1824 	if (Tk_IsMapped(butPtr->tkwin) && !(butPtr->flags & REDRAW_PENDING)) {
1825 	    Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr);
1826 	    butPtr->flags |= REDRAW_PENDING;
1827 	}
1828     }
1829 }
1830 
1831 /*
1832  *----------------------------------------------------------------------
1833  *
1834  * ButtonSelectImageProc --
1835  *
1836  *	This procedure is invoked by the image code whenever the manager
1837  *	for an image does something that affects the size of contents
1838  *	of the image displayed in a button when it is selected.
1839  *
1840  * Results:
1841  *	None.
1842  *
1843  * Side effects:
1844  *	May arrange for the button to get redisplayed.
1845  *
1846  *----------------------------------------------------------------------
1847  */
1848 
1849 static void
ButtonSelectImageProc(clientData,x,y,width,height,imgWidth,imgHeight)1850 ButtonSelectImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
1851     ClientData clientData;		/* Pointer to widget record. */
1852     int x, y;				/* Upper left pixel (within image)
1853 					 * that must be redisplayed. */
1854     int width, height;			/* Dimensions of area to redisplay
1855 					 * (may be <= 0). */
1856     int imgWidth, imgHeight;		/* New dimensions of image. */
1857 {
1858     register Button *butPtr = (Button *) clientData;
1859 
1860     /*
1861      * Don't recompute geometry:  it's controlled by the primary image.
1862      */
1863 
1864     if ((butPtr->flags & SELECTED) && (butPtr->tkwin != NULL)
1865 	    && Tk_IsMapped(butPtr->tkwin)
1866 	    && !(butPtr->flags & REDRAW_PENDING)) {
1867 	Tcl_DoWhenIdle(DisplayButton, (ClientData) butPtr);
1868 	butPtr->flags |= REDRAW_PENDING;
1869     }
1870 }
1871