OSDN Git Service

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