1 /*
2  * tkMessage.c --
3  *
4  *	This module implements a message widgets for the Tk toolkit. A message
5  *	widget displays a multi-line string in a window according to a
6  *	particular aspect ratio.
7  *
8  * Copyright © 1990-1994 The Regents of the University of California.
9  * Copyright © 1994-1997 Sun Microsystems, Inc.
10  * Copyright © 1998-2000 Ajuba Solutions.
11  *
12  * See the file "license.terms" for information on usage and redistribution of
13  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15 
16 #include "tkInt.h"
17 #include "default.h"
18 
19 /*
20  * A data structure of the following type is kept for each message widget
21  * managed by this file:
22  */
23 
24 typedef struct {
25     Tk_Window tkwin;		/* Window that embodies the message. NULL
26 				 * means that the window has been destroyed
27 				 * but the data structures haven't yet been
28 				 * cleaned up.*/
29     Tk_OptionTable optionTable;	/* Table that defines options available for
30 				 * this widget. */
31     Display *display;		/* Display containing widget. Used, among
32 				 * other things, so that resources can be
33 				 * freed even after tkwin has gone away. */
34     Tcl_Interp *interp;		/* Interpreter associated with message. */
35     Tcl_Command widgetCmd;	/* Token for message's widget command. */
36 
37     /*
38      * Information used when displaying widget:
39      */
40 
41     char *string;		/* String displayed in message. */
42     int numChars;		/* Number of characters in string, not
43 				 * including terminating NULL. */
44     char *textVarName;		/* Name of variable (malloc'ed) or NULL.
45 				 * If non-NULL, message displays the contents
46 				 * of this variable. */
47     Tk_3DBorder border;		/* Structure used to draw 3-D border and
48 				 * background. NULL means a border hasn't been
49 				 * created yet. */
50     int borderWidth;		/* Width of border. */
51     int relief;			/* 3-D effect: TK_RELIEF_RAISED, etc. */
52     int highlightWidth;		/* Width in pixels of highlight to draw
53 				 * around widget when it has the focus.
54 				 * <= 0 means don't draw a highlight. */
55     XColor *highlightBgColorPtr;
56 				/* Color for drawing traversal highlight
57 				 * area when highlight is off. */
58     XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
59     Tk_Font tkfont;		/* Information about text font, or NULL. */
60     XColor *fgColorPtr;		/* Foreground color in normal mode. */
61     Tcl_Obj *padXPtr, *padYPtr;	/* Tcl_Obj rep's of padX, padY values. */
62     int padX, padY;		/* User-requested extra space around text. */
63     int width;			/* User-requested width, in pixels. 0 means
64 				 * compute width using aspect ratio below. */
65     int aspect;			/* Desired aspect ratio for window
66 				 * (100*width/height). */
67     int msgWidth;		/* Width in pixels needed to display
68 				 * message. */
69     int msgHeight;		/* Height in pixels needed to display
70 				 * message. */
71     Tk_Anchor anchor;		/* Where to position text within window region
72 				 * if window is larger or smaller than
73 				 * needed. */
74     Tk_Justify justify;		/* Justification for text. */
75 
76     GC textGC;			/* GC for drawing text in normal mode. */
77     Tk_TextLayout textLayout;	/* Saved layout information. */
78 
79     /*
80      * Miscellaneous information:
81      */
82 
83     Tk_Cursor cursor;		/* Current cursor for window, or None. */
84     char *takeFocus;		/* Value of -takefocus option; not used in the
85 				 * C code, but used by keyboard traversal
86 				 * scripts. Malloc'ed, but may be NULL. */
87     int flags;			/* Various flags; see below for
88 				 * definitions. */
89 } Message;
90 
91 /*
92  * Flag bits for messages:
93  *
94  * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
95  *				has already been queued to redraw
96  *				this window.
97  * GOT_FOCUS:			Non-zero means this button currently
98  *				has the input focus.
99  * MESSAGE_DELETED:		The message has been effectively deleted.
100  */
101 
102 #define REDRAW_PENDING		1
103 #define GOT_FOCUS		4
104 #define MESSAGE_DELETED		8
105 
106 /*
107  * Information used for argv parsing.
108  */
109 
110 static const Tk_OptionSpec optionSpecs[] = {
111     {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", DEF_MESSAGE_ANCHOR,
112 	 TCL_INDEX_NONE, offsetof(Message, anchor), 0, 0, 0},
113     {TK_OPTION_INT, "-aspect", "aspect", "Aspect", DEF_MESSAGE_ASPECT,
114 	 TCL_INDEX_NONE, offsetof(Message, aspect), 0, 0, 0},
115     {TK_OPTION_BORDER, "-background", "background", "Background",
116 	 DEF_MESSAGE_BG_COLOR, TCL_INDEX_NONE, offsetof(Message, border), 0,
117 	 DEF_MESSAGE_BG_MONO, 0},
118     {TK_OPTION_SYNONYM, "-bd", NULL, NULL, NULL,
119 	 0, TCL_INDEX_NONE, 0, "-borderwidth", 0},
120     {TK_OPTION_SYNONYM, "-bg", NULL, NULL, NULL,
121 	 0, TCL_INDEX_NONE, 0, "-background", 0},
122     {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
123 	 DEF_MESSAGE_BORDER_WIDTH, TCL_INDEX_NONE,
124 	 offsetof(Message, borderWidth), 0, 0, 0},
125     {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
126 	 DEF_MESSAGE_CURSOR, TCL_INDEX_NONE, offsetof(Message, cursor),
127 	 TK_OPTION_NULL_OK, 0, 0},
128     {TK_OPTION_SYNONYM, "-fg", NULL, NULL, NULL,
129 	 0, TCL_INDEX_NONE, 0, "-foreground", 0},
130     {TK_OPTION_FONT, "-font", "font", "Font",
131 	DEF_MESSAGE_FONT, TCL_INDEX_NONE, offsetof(Message, tkfont), 0, 0, 0},
132     {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
133 	DEF_MESSAGE_FG, TCL_INDEX_NONE, offsetof(Message, fgColorPtr), 0, 0, 0},
134     {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
135 	 "HighlightBackground", DEF_MESSAGE_HIGHLIGHT_BG, TCL_INDEX_NONE,
136 	 offsetof(Message, highlightBgColorPtr), 0, 0, 0},
137     {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
138 	 DEF_MESSAGE_HIGHLIGHT, TCL_INDEX_NONE, offsetof(Message, highlightColorPtr),
139 	 0, 0, 0},
140     {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
141 	"HighlightThickness", DEF_MESSAGE_HIGHLIGHT_WIDTH, TCL_INDEX_NONE,
142 	 offsetof(Message, highlightWidth), 0, 0, 0},
143     {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
144 	DEF_MESSAGE_JUSTIFY, TCL_INDEX_NONE, offsetof(Message, justify), 0, 0, 0},
145     {TK_OPTION_PIXELS, "-padx", "padX", "Pad",
146 	 DEF_MESSAGE_PADX, offsetof(Message, padXPtr),
147 	 offsetof(Message, padX), 0, 0, 0},
148     {TK_OPTION_PIXELS, "-pady", "padY", "Pad",
149 	 DEF_MESSAGE_PADY, offsetof(Message, padYPtr),
150 	 offsetof(Message, padY), 0, 0, 0},
151     {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
152 	DEF_MESSAGE_RELIEF, TCL_INDEX_NONE, offsetof(Message, relief), 0, 0, 0},
153     {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
154 	DEF_MESSAGE_TAKE_FOCUS, TCL_INDEX_NONE, offsetof(Message, takeFocus),
155 	TK_OPTION_NULL_OK, 0, 0},
156     {TK_OPTION_STRING, "-text", "text", "Text",
157 	DEF_MESSAGE_TEXT, TCL_INDEX_NONE, offsetof(Message, string), 0, 0, 0},
158     {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
159 	DEF_MESSAGE_TEXT_VARIABLE, TCL_INDEX_NONE, offsetof(Message, textVarName),
160 	TK_OPTION_NULL_OK, 0, 0},
161     {TK_OPTION_PIXELS, "-width", "width", "Width",
162 	DEF_MESSAGE_WIDTH, TCL_INDEX_NONE, offsetof(Message, width), 0, 0 ,0},
163     {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0}
164 };
165 
166 /*
167  * Forward declarations for functions defined later in this file:
168  */
169 
170 static void		MessageCmdDeletedProc(ClientData clientData);
171 static void		MessageEventProc(ClientData clientData,
172 			    XEvent *eventPtr);
173 static char *		MessageTextVarProc(ClientData clientData,
174 			    Tcl_Interp *interp, const char *name1,
175 			    const char *name2, int flags);
176 static int		MessageWidgetObjCmd(ClientData clientData,
177 			    Tcl_Interp *interp, int objc,
178 			    Tcl_Obj *const objv[]);
179 static void		MessageWorldChanged(ClientData instanceData);
180 static void		ComputeMessageGeometry(Message *msgPtr);
181 static int		ConfigureMessage(Tcl_Interp *interp, Message *msgPtr,
182 			    int objc, Tcl_Obj *const objv[], int flags);
183 static void		DestroyMessage(void *memPtr);
184 static void		DisplayMessage(ClientData clientData);
185 
186 /*
187  * The structure below defines message class behavior by means of functions
188  * that can be invoked from generic window code.
189  */
190 
191 static const Tk_ClassProcs messageClass = {
192     sizeof(Tk_ClassProcs),	/* size */
193     MessageWorldChanged,	/* worldChangedProc */
194     NULL,			/* createProc */
195     NULL			/* modalProc */
196 };
197 
198 /*
199  *--------------------------------------------------------------
200  *
201  * Tk_MessageObjCmd --
202  *
203  *	This function is invoked to process the "message" Tcl command. See the
204  *	user documentation for details on what it does.
205  *
206  * Results:
207  *	A standard Tcl result.
208  *
209  * Side effects:
210  *	See the user documentation.
211  *
212  *--------------------------------------------------------------
213  */
214 
215 int
Tk_MessageObjCmd(ClientData dummy,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])216 Tk_MessageObjCmd(
217     ClientData dummy,	/* NULL. */
218     Tcl_Interp *interp,		/* Current interpreter. */
219     int objc,			/* Number of arguments. */
220     Tcl_Obj *const objv[])	/* Argument strings. */
221 {
222     Message *msgPtr;
223     Tk_OptionTable optionTable;
224     Tk_Window tkwin;
225     (void)dummy;
226 
227     if (objc < 2) {
228 	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?");
229 	return TCL_ERROR;
230     }
231 
232     tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
233 	    Tcl_GetString(objv[1]), NULL);
234     if (tkwin == NULL) {
235 	return TCL_ERROR;
236     }
237 
238     /*
239      * Create the option table for this widget class. If it has already been
240      * created, the cached pointer will be returned.
241      */
242 
243     optionTable = Tk_CreateOptionTable(interp, optionSpecs);
244 
245     msgPtr = (Message *)ckalloc(sizeof(Message));
246     memset(msgPtr, 0, sizeof(Message));
247 
248     /*
249      * Set values for those fields that don't take a 0 or NULL value.
250      */
251 
252     msgPtr->tkwin = tkwin;
253     msgPtr->display = Tk_Display(tkwin);
254     msgPtr->interp = interp;
255     msgPtr->widgetCmd = Tcl_CreateObjCommand(interp,
256 	    Tk_PathName(msgPtr->tkwin), MessageWidgetObjCmd, msgPtr,
257 	    MessageCmdDeletedProc);
258     msgPtr->optionTable = optionTable;
259     msgPtr->relief = TK_RELIEF_FLAT;
260     msgPtr->textGC = NULL;
261     msgPtr->anchor = TK_ANCHOR_CENTER;
262     msgPtr->aspect = 150;
263     msgPtr->justify = TK_JUSTIFY_LEFT;
264     msgPtr->cursor = NULL;
265 
266     Tk_SetClass(msgPtr->tkwin, "Message");
267     Tk_SetClassProcs(msgPtr->tkwin, &messageClass, msgPtr);
268     Tk_CreateEventHandler(msgPtr->tkwin,
269 	    ExposureMask|StructureNotifyMask|FocusChangeMask,
270 	    MessageEventProc, msgPtr);
271     if (Tk_InitOptions(interp, msgPtr, optionTable, tkwin) != TCL_OK) {
272 	Tk_DestroyWindow(msgPtr->tkwin);
273 	return TCL_ERROR;
274     }
275 
276     if (ConfigureMessage(interp, msgPtr, objc-2, objv+2, 0) != TCL_OK) {
277 	Tk_DestroyWindow(msgPtr->tkwin);
278 	return TCL_ERROR;
279     }
280 
281     Tcl_SetObjResult(interp, Tk_NewWindowObj(msgPtr->tkwin));
282     return TCL_OK;
283 }
284 
285 /*
286  *--------------------------------------------------------------
287  *
288  * MessageWidgetObjCmd --
289  *
290  *	This function is invoked to process the Tcl command that corresponds
291  *	to a widget managed by this module. See the user documentation for
292  *	details on what it does.
293  *
294  * Results:
295  *	A standard Tcl result.
296  *
297  * Side effects:
298  *	See the user documentation.
299  *
300  *--------------------------------------------------------------
301  */
302 
303 static int
MessageWidgetObjCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])304 MessageWidgetObjCmd(
305     ClientData clientData,	/* Information about message widget. */
306     Tcl_Interp *interp,		/* Current interpreter. */
307     int objc,			/* Number of arguments. */
308     Tcl_Obj *const objv[])	/* Argument strings. */
309 {
310     Message *msgPtr = (Message *)clientData;
311     static const char *const optionStrings[] = { "cget", "configure", NULL };
312     enum options { MESSAGE_CGET, MESSAGE_CONFIGURE };
313     int index;
314     int result = TCL_OK;
315     Tcl_Obj *objPtr;
316 
317     if (objc < 2) {
318 	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
319 	return TCL_ERROR;
320     }
321 
322     if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
323 	    sizeof(char *), "option", 0, &index) != TCL_OK) {
324 	return TCL_ERROR;
325     }
326 
327     Tcl_Preserve(msgPtr);
328 
329     switch ((enum options) index) {
330     case MESSAGE_CGET:
331 	if (objc != 3) {
332 	    Tcl_WrongNumArgs(interp, 2, objv, "option");
333 	    result = TCL_ERROR;
334 	} else {
335 	    objPtr = Tk_GetOptionValue(interp, msgPtr,
336 		    msgPtr->optionTable, objv[2], msgPtr->tkwin);
337 	    if (objPtr == NULL) {
338 		result = TCL_ERROR;
339 	    } else {
340 		Tcl_SetObjResult(interp, objPtr);
341 		result = TCL_OK;
342 	    }
343 	}
344 	break;
345     case MESSAGE_CONFIGURE:
346 	if (objc <= 3) {
347 	    objPtr = Tk_GetOptionInfo(interp, msgPtr,
348 		    msgPtr->optionTable, (objc == 3) ? objv[2] : NULL,
349 		    msgPtr->tkwin);
350 	    if (objPtr == NULL) {
351 		result = TCL_ERROR;
352 	    } else {
353 		Tcl_SetObjResult(interp, objPtr);
354 		result = TCL_OK;
355 	    }
356 	} else {
357 	    result = ConfigureMessage(interp, msgPtr, objc-2, objv+2, 0);
358 	}
359 	break;
360     }
361 
362     Tcl_Release(msgPtr);
363     return result;
364 }
365 
366 /*
367  *----------------------------------------------------------------------
368  *
369  * DestroyMessage --
370  *
371  *	This function is invoked by Tcl_EventuallyFree or Tcl_Release to clean
372  *	up the internal structure of a message at a safe time (when no-one is
373  *	using it anymore).
374  *
375  * Results:
376  *	None.
377  *
378  * Side effects:
379  *	Everything associated with the message is freed up.
380  *
381  *----------------------------------------------------------------------
382  */
383 
384 static void
DestroyMessage(void * memPtr)385 DestroyMessage(
386     void *memPtr)		/* Info about message widget. */
387 {
388     Message *msgPtr = (Message *) memPtr;
389 
390     msgPtr->flags |= MESSAGE_DELETED;
391 
392     Tcl_DeleteCommandFromToken(msgPtr->interp, msgPtr->widgetCmd);
393     if (msgPtr->flags & REDRAW_PENDING) {
394 	Tcl_CancelIdleCall(DisplayMessage, msgPtr);
395     }
396 
397     /*
398      * Free up all the stuff that requires special handling, then let
399      * Tk_FreeConfigOptions handle all the standard option-related stuff.
400      */
401 
402     if (msgPtr->textGC != NULL) {
403 	Tk_FreeGC(msgPtr->display, msgPtr->textGC);
404     }
405     if (msgPtr->textLayout != NULL) {
406 	Tk_FreeTextLayout(msgPtr->textLayout);
407     }
408     if (msgPtr->textVarName != NULL) {
409 	Tcl_UntraceVar2(msgPtr->interp, msgPtr->textVarName, NULL,
410 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
411 		MessageTextVarProc, msgPtr);
412     }
413     Tk_FreeConfigOptions((char *) msgPtr, msgPtr->optionTable, msgPtr->tkwin);
414     msgPtr->tkwin = NULL;
415     ckfree(msgPtr);
416 }
417 
418 /*
419  *----------------------------------------------------------------------
420  *
421  * ConfigureMessage --
422  *
423  *	This function is called to process an argv/argc list, plus the Tk
424  *	option database, in order to configure (or reconfigure) a message
425  *	widget.
426  *
427  * Results:
428  *	The return value is a standard Tcl result. If TCL_ERROR is returned,
429  *	then the interp's result contains an error message.
430  *
431  * Side effects:
432  *	Configuration information, such as text string, colors, font, etc. get
433  *	set for msgPtr; old resources get freed, if there were any.
434  *
435  *----------------------------------------------------------------------
436  */
437 
438 static int
ConfigureMessage(Tcl_Interp * interp,Message * msgPtr,int objc,Tcl_Obj * const objv[],int flags)439 ConfigureMessage(
440     Tcl_Interp *interp,		/* Used for error reporting. */
441     Message *msgPtr,	/* Information about widget; may or may not
442 				 * already have values for some fields. */
443     int objc,			/* Number of valid entries in argv. */
444     Tcl_Obj *const objv[],	/* Arguments. */
445     int flags)			/* Flags to pass to Tk_ConfigureWidget. */
446 {
447     Tk_SavedOptions savedOptions;
448     (void)flags;
449 
450     /*
451      * Eliminate any existing trace on a variable monitored by the message.
452      */
453 
454     if (msgPtr->textVarName != NULL) {
455 	Tcl_UntraceVar2(interp, msgPtr->textVarName, NULL,
456 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
457 		MessageTextVarProc, msgPtr);
458     }
459 
460     if (Tk_SetOptions(interp, msgPtr, msgPtr->optionTable, objc, objv,
461 	    msgPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
462 	Tk_RestoreSavedOptions(&savedOptions);
463 	return TCL_ERROR;
464     }
465 
466     /*
467      * If the message is to display the value of a variable, then set up a
468      * trace on the variable's value, create the variable if it doesn't exist,
469      * and fetch its current value.
470      */
471 
472     if (msgPtr->textVarName != NULL) {
473 	const char *value;
474 
475 	value = Tcl_GetVar2(interp, msgPtr->textVarName, NULL, TCL_GLOBAL_ONLY);
476 	if (value == NULL) {
477 	    Tcl_SetVar2(interp, msgPtr->textVarName, NULL, msgPtr->string,
478 		    TCL_GLOBAL_ONLY);
479 	} else {
480 	    if (msgPtr->string != NULL) {
481 		ckfree(msgPtr->string);
482 	    }
483 	    msgPtr->string = strcpy((char *)ckalloc(strlen(value) + 1), value);
484 	}
485 	Tcl_TraceVar2(interp, msgPtr->textVarName, NULL,
486 		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
487 		MessageTextVarProc, msgPtr);
488     }
489 
490     /*
491      * A few other options need special processing, such as setting the
492      * background from a 3-D border or handling special defaults that couldn't
493      * be specified to Tk_ConfigureWidget.
494      */
495 
496     msgPtr->numChars = Tcl_NumUtfChars(msgPtr->string, -1);
497 
498     if (msgPtr->highlightWidth < 0) {
499 	msgPtr->highlightWidth = 0;
500     }
501 
502     Tk_FreeSavedOptions(&savedOptions);
503     MessageWorldChanged(msgPtr);
504     return TCL_OK;
505 }
506 
507 /*
508  *---------------------------------------------------------------------------
509  *
510  * MessageWorldChanged --
511  *
512  *	This function is called when the world has changed in some way and the
513  *	widget needs to recompute all its graphics contexts and determine its
514  *	new geometry.
515  *
516  * Results:
517  *	None.
518  *
519  * Side effects:
520  *	Message will be relayed out and redisplayed.
521  *
522  *---------------------------------------------------------------------------
523  */
524 
525 static void
MessageWorldChanged(ClientData instanceData)526 MessageWorldChanged(
527     ClientData instanceData)	/* Information about widget. */
528 {
529     XGCValues gcValues;
530     GC gc = NULL;
531     Tk_FontMetrics fm;
532     Message *msgPtr = (Message *)instanceData;
533 
534     if (msgPtr->border != NULL) {
535 	Tk_SetBackgroundFromBorder(msgPtr->tkwin, msgPtr->border);
536     }
537 
538     gcValues.font = Tk_FontId(msgPtr->tkfont);
539     gcValues.foreground = msgPtr->fgColorPtr->pixel;
540     gc = Tk_GetGC(msgPtr->tkwin, GCForeground | GCFont, &gcValues);
541     if (msgPtr->textGC != NULL) {
542 	Tk_FreeGC(msgPtr->display, msgPtr->textGC);
543     }
544     msgPtr->textGC = gc;
545 
546     Tk_GetFontMetrics(msgPtr->tkfont, &fm);
547     if (msgPtr->padX < 0) {
548 	msgPtr->padX = fm.ascent / 2;
549     }
550     if (msgPtr->padY == -1) {
551 	msgPtr->padY = fm.ascent / 4;
552     }
553 
554     /*
555      * Recompute the desired geometry for the window, and arrange for the
556      * window to be redisplayed.
557      */
558 
559     ComputeMessageGeometry(msgPtr);
560     if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
561 	    && !(msgPtr->flags & REDRAW_PENDING)) {
562 	Tcl_DoWhenIdle(DisplayMessage, msgPtr);
563 	msgPtr->flags |= REDRAW_PENDING;
564     }
565 }
566 
567 /*
568  *--------------------------------------------------------------
569  *
570  * ComputeMessageGeometry --
571  *
572  *	Compute the desired geometry for a message window, taking into account
573  *	the desired aspect ratio for the window.
574  *
575  * Results:
576  *	None.
577  *
578  * Side effects:
579  *	Tk_GeometryRequest is called to inform the geometry manager of the
580  *	desired geometry for this window.
581  *
582  *--------------------------------------------------------------
583  */
584 
585 static void
ComputeMessageGeometry(Message * msgPtr)586 ComputeMessageGeometry(
587     Message *msgPtr)	/* Information about window. */
588 {
589     int width, inc, height;
590     int thisWidth, thisHeight, maxWidth;
591     int aspect, lowerBound, upperBound, inset;
592 
593     Tk_FreeTextLayout(msgPtr->textLayout);
594 
595     inset = msgPtr->borderWidth + msgPtr->highlightWidth;
596 
597     /*
598      * Compute acceptable bounds for the final aspect ratio.
599      */
600 
601     aspect = msgPtr->aspect/10;
602     if (aspect < 5) {
603 	aspect = 5;
604     }
605     lowerBound = msgPtr->aspect - aspect;
606     upperBound = msgPtr->aspect + aspect;
607 
608     /*
609      * Do the computation in multiple passes: start off with a very wide
610      * window, and compute its height. Then change the width and try again.
611      * Reduce the size of the change and iterate until dimensions are found
612      * that approximate the desired aspect ratio. Or, if the user gave an
613      * explicit width then just use that.
614      */
615 
616     if (msgPtr->width > 0) {
617 	width = msgPtr->width;
618 	inc = 0;
619     } else {
620 	width = WidthOfScreen(Tk_Screen(msgPtr->tkwin))/2;
621 	inc = width/2;
622     }
623 
624     for ( ; ; inc /= 2) {
625 	msgPtr->textLayout = Tk_ComputeTextLayout(msgPtr->tkfont,
626 		msgPtr->string, msgPtr->numChars, width, msgPtr->justify,
627 		0, &thisWidth, &thisHeight);
628 	maxWidth = thisWidth + 2 * (inset + msgPtr->padX);
629 	height = thisHeight + 2 * (inset + msgPtr->padY);
630 
631 	if (inc <= 2) {
632 	    break;
633 	}
634 	aspect = (100 * maxWidth) / height;
635 
636 	if (aspect < lowerBound) {
637 	    width += inc;
638 	} else if (aspect > upperBound) {
639 	    width -= inc;
640 	} else {
641 	    break;
642 	}
643 	Tk_FreeTextLayout(msgPtr->textLayout);
644     }
645     msgPtr->msgWidth = thisWidth;
646     msgPtr->msgHeight = thisHeight;
647     Tk_GeometryRequest(msgPtr->tkwin, maxWidth, height);
648     Tk_SetInternalBorder(msgPtr->tkwin, inset);
649 }
650 
651 /*
652  *--------------------------------------------------------------
653  *
654  * DisplayMessage --
655  *
656  *	This function redraws the contents of a message window.
657  *
658  * Results:
659  *	None.
660  *
661  * Side effects:
662  *	Information appears on the screen.
663  *
664  *--------------------------------------------------------------
665  */
666 
667 static void
DisplayMessage(ClientData clientData)668 DisplayMessage(
669     ClientData clientData)	/* Information about window. */
670 {
671     Message *msgPtr = (Message *)clientData;
672     Tk_Window tkwin = msgPtr->tkwin;
673     int x, y;
674     int borderWidth = msgPtr->highlightWidth;
675 
676     msgPtr->flags &= ~REDRAW_PENDING;
677     if ((msgPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
678 	return;
679     }
680     if (msgPtr->border != NULL) {
681 	borderWidth += msgPtr->borderWidth;
682     }
683     if (msgPtr->relief == TK_RELIEF_FLAT) {
684 	borderWidth = msgPtr->highlightWidth;
685     }
686     Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), msgPtr->border,
687 	    borderWidth, borderWidth,
688 	    Tk_Width(tkwin) - 2 * borderWidth,
689 	    Tk_Height(tkwin) - 2 * borderWidth,
690 	    0, TK_RELIEF_FLAT);
691 
692     /*
693      * Compute starting y-location for message based on message size and
694      * anchor option.
695      */
696 
697     TkComputeAnchor(msgPtr->anchor, tkwin, msgPtr->padX, msgPtr->padY,
698 	    msgPtr->msgWidth, msgPtr->msgHeight, &x, &y);
699     Tk_DrawTextLayout(Tk_Display(tkwin), Tk_WindowId(tkwin), msgPtr->textGC,
700 	    msgPtr->textLayout, x, y, 0, -1);
701 
702     if (borderWidth > msgPtr->highlightWidth) {
703 	Tk_Draw3DRectangle(tkwin, Tk_WindowId(tkwin), msgPtr->border,
704 		msgPtr->highlightWidth, msgPtr->highlightWidth,
705 		Tk_Width(tkwin) - 2*msgPtr->highlightWidth,
706 		Tk_Height(tkwin) - 2*msgPtr->highlightWidth,
707 		msgPtr->borderWidth, msgPtr->relief);
708     }
709     if (msgPtr->highlightWidth != 0) {
710 	GC fgGC, bgGC;
711 
712 	bgGC = Tk_GCForColor(msgPtr->highlightBgColorPtr, Tk_WindowId(tkwin));
713 	if (msgPtr->flags & GOT_FOCUS) {
714 	    fgGC = Tk_GCForColor(msgPtr->highlightColorPtr,Tk_WindowId(tkwin));
715 	    TkpDrawHighlightBorder(tkwin, fgGC, bgGC, msgPtr->highlightWidth,
716 		    Tk_WindowId(tkwin));
717 	} else {
718 	    TkpDrawHighlightBorder(tkwin, bgGC, bgGC, msgPtr->highlightWidth,
719 		    Tk_WindowId(tkwin));
720 	}
721     }
722 }
723 
724 /*
725  *--------------------------------------------------------------
726  *
727  * MessageEventProc --
728  *
729  *	This function is invoked by the Tk dispatcher for various events on
730  *	messages.
731  *
732  * Results:
733  *	None.
734  *
735  * Side effects:
736  *	When the window gets deleted, internal structures get cleaned up.
737  *	When it gets exposed, it is redisplayed.
738  *
739  *--------------------------------------------------------------
740  */
741 
742 static void
MessageEventProc(ClientData clientData,XEvent * eventPtr)743 MessageEventProc(
744     ClientData clientData,	/* Information about window. */
745     XEvent *eventPtr)		/* Information about event. */
746 {
747     Message *msgPtr = (Message *)clientData;
748 
749     if (((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0))
750 	    || (eventPtr->type == ConfigureNotify)) {
751 	goto redraw;
752     } else if (eventPtr->type == DestroyNotify) {
753 	DestroyMessage(clientData);
754     } else if (eventPtr->type == FocusIn) {
755 	if (eventPtr->xfocus.detail != NotifyInferior) {
756 	    msgPtr->flags |= GOT_FOCUS;
757 	    if (msgPtr->highlightWidth > 0) {
758 		goto redraw;
759 	    }
760 	}
761     } else if (eventPtr->type == FocusOut) {
762 	if (eventPtr->xfocus.detail != NotifyInferior) {
763 	    msgPtr->flags &= ~GOT_FOCUS;
764 	    if (msgPtr->highlightWidth > 0) {
765 		goto redraw;
766 	    }
767 	}
768     }
769     return;
770 
771   redraw:
772     if ((msgPtr->tkwin != NULL) && !(msgPtr->flags & REDRAW_PENDING)) {
773 	Tcl_DoWhenIdle(DisplayMessage, msgPtr);
774 	msgPtr->flags |= REDRAW_PENDING;
775     }
776 }
777 
778 /*
779  *----------------------------------------------------------------------
780  *
781  * MessageCmdDeletedProc --
782  *
783  *	This function is invoked when a widget command is deleted. If the
784  *	widget isn't already in the process of being destroyed, this command
785  *	destroys it.
786  *
787  * Results:
788  *	None.
789  *
790  * Side effects:
791  *	The widget is destroyed.
792  *
793  *----------------------------------------------------------------------
794  */
795 
796 static void
MessageCmdDeletedProc(ClientData clientData)797 MessageCmdDeletedProc(
798     ClientData clientData)	/* Pointer to widget record for widget. */
799 {
800     Message *msgPtr = (Message *)clientData;
801 
802     /*
803      * This function could be invoked either because the window was destroyed
804      * and the command was then deleted (in which case tkwin is NULL) or
805      * because the command was deleted, and then this function destroys the
806      * widget.
807      */
808 
809     if (!(msgPtr->flags & MESSAGE_DELETED)) {
810 	Tk_DestroyWindow(msgPtr->tkwin);
811     }
812 }
813 
814 /*
815  *--------------------------------------------------------------
816  *
817  * MessageTextVarProc --
818  *
819  *	This function is invoked when someone changes the variable whose
820  *	contents are to be displayed in a message.
821  *
822  * Results:
823  *	NULL is always returned.
824  *
825  * Side effects:
826  *	The text displayed in the message will change to match the variable.
827  *
828  *--------------------------------------------------------------
829  */
830 
831 static char *
MessageTextVarProc(ClientData clientData,Tcl_Interp * interp,const char * name1,const char * name2,int flags)832 MessageTextVarProc(
833     ClientData clientData,	/* Information about message. */
834     Tcl_Interp *interp,		/* Interpreter containing variable. */
835     const char *name1,		/* Name of variable. */
836     const char *name2,		/* Second part of variable name. */
837     int flags)			/* Information about what happened. */
838 {
839     Message *msgPtr = (Message *)clientData;
840     const char *value;
841     (void)name1;
842     (void)name2;
843 
844     /*
845      * If the variable is unset, then immediately recreate it unless the whole
846      * interpreter is going away.
847      */
848 
849     if (flags & TCL_TRACE_UNSETS) {
850         if (!Tcl_InterpDeleted(interp) && msgPtr->textVarName) {
851             ClientData probe = NULL;
852 
853             do {
854                 probe = Tcl_VarTraceInfo(interp,
855                         msgPtr->textVarName,
856                         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
857                         MessageTextVarProc, probe);
858                 if (probe == (ClientData)msgPtr) {
859                     break;
860                 }
861             } while (probe);
862             if (probe) {
863                 /*
864                  * We were able to fetch the unset trace for our
865                  * textVarName, which means it is not unset and not
866                  * the cause of this unset trace. Instead some outdated
867                  * former variable must be, and we should ignore it.
868                  */
869                 return NULL;
870             }
871 	    Tcl_SetVar2(interp, msgPtr->textVarName, NULL, msgPtr->string,
872 		    TCL_GLOBAL_ONLY);
873 	    Tcl_TraceVar2(interp, msgPtr->textVarName, NULL,
874 		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
875 		    MessageTextVarProc, clientData);
876 	}
877 	return NULL;
878     }
879 
880     value = Tcl_GetVar2(interp, msgPtr->textVarName, NULL, TCL_GLOBAL_ONLY);
881     if (value == NULL) {
882 	value = "";
883     }
884     if (msgPtr->string != NULL) {
885 	ckfree(msgPtr->string);
886     }
887     msgPtr->numChars = Tcl_NumUtfChars(value, -1);
888     msgPtr->string = (char *)ckalloc(strlen(value) + 1);
889     strcpy(msgPtr->string, value);
890     ComputeMessageGeometry(msgPtr);
891 
892     if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
893 	    && !(msgPtr->flags & REDRAW_PENDING)) {
894 	Tcl_DoWhenIdle(DisplayMessage, msgPtr);
895 	msgPtr->flags |= REDRAW_PENDING;
896     }
897     return NULL;
898 }
899 
900 /*
901  * Local Variables:
902  * mode: c
903  * c-basic-offset: 4
904  * fill-column: 78
905  * End:
906  */
907