]> www.wagner.pp.ru Git - oss/ck.git/blob - ckMessage.c
Ck console graphics toolkit
[oss/ck.git] / ckMessage.c
1 /* 
2  * ckMessage.c --
3  *
4  *      This module implements a message widgets for the
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-1995 Sun Microsystems, Inc.
10  * Copyright (c) 1995 Christian Werner
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
16 #include "ckPort.h"
17 #include "ck.h"
18 #include "default.h"
19
20 /*
21  * A data structure of the following type is kept for each message
22  * widget managed by this file:
23  */
24
25 typedef struct {
26     CkWindow *winPtr;           /* Window that embodies the message.  NULL
27                                  * means that the window has been destroyed
28                                  * but the data structures haven't yet been
29                                  * cleaned up.*/
30     Tcl_Interp *interp;         /* Interpreter associated with message. */
31     Tcl_Command widgetCmd;      /* Token for message's widget command. */
32     char *string;               /* String displayed in message. */
33     int numChars;               /* Number of characters in string, not
34                                  * including terminating NULL character. */
35     char *textVarName;          /* Name of variable (malloc'ed) or NULL.
36                                  * If non-NULL, message displays the contents
37                                  * of this variable. */
38
39     /*
40      * Information used when displaying widget:
41      */
42
43     int bg, fg;                 /* Foreground and background colors. */
44     int attr;                   /* Video attributes. */
45     Ck_Anchor anchor;           /* Where to position text within window region
46                                  * if window is larger or smaller than
47                                  * needed. */
48     int width;                  /* User-requested width. 0 means compute
49                                  * width using aspect ratio below. */
50     int aspect;                 /* Desired aspect ratio for window
51                                  * (100*width/height). */
52     int lineLength;             /* Length of each line.  Computed
53                                  * from width and/or aspect. */
54     int msgHeight;              /* Total lines needed to display message. */
55     Ck_Justify justify;         /* Justification for text. */
56
57     /*
58      * Miscellaneous information:
59      */
60
61     char *takeFocus;            /* Value of -takefocus option;  not used in
62                                  * the C code, but used by keyboard traversal
63                                  * scripts.  Malloc'ed, but may be NULL. */
64     int flags;                  /* Various flags;  see below for
65                                  * definitions. */
66 } Message;
67
68 /*
69  * Flag bits for messages:
70  *
71  * REDRAW_PENDING:              Non-zero means a DoWhenIdle handler
72  *                              has already been queued to redraw
73  *                              this window.
74  */
75
76 #define REDRAW_PENDING          1
77
78 /*
79  * Information used for argv parsing.
80  */
81
82 static Ck_ConfigSpec configSpecs[] = {
83     {CK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
84         DEF_MESSAGE_ANCHOR, Ck_Offset(Message, anchor), 0},
85     {CK_CONFIG_INT, "-aspect", "aspect", "Aspect",
86         DEF_MESSAGE_ASPECT, Ck_Offset(Message, aspect), 0},
87     {CK_CONFIG_ATTR, "-attributes", "attributes", "Attributes",
88         DEF_MESSAGE_ATTR, Ck_Offset(Message, attr), 0},
89     {CK_CONFIG_COLOR, "-background", "background", "Background",
90         DEF_MESSAGE_BG_COLOR, Ck_Offset(Message, bg),
91         CK_CONFIG_COLOR_ONLY},
92     {CK_CONFIG_COLOR, "-background", "background", "Background",
93         DEF_MESSAGE_BG_MONO, Ck_Offset(Message, bg),
94         CK_CONFIG_MONO_ONLY},
95     {CK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
96         (char *) NULL, 0, 0},
97     {CK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
98         (char *) NULL, 0, 0},
99     {CK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
100         DEF_MESSAGE_FG_COLOR, Ck_Offset(Message, fg),
101         CK_CONFIG_COLOR_ONLY},
102     {CK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
103         DEF_MESSAGE_FG_MONO, Ck_Offset(Message, fg),
104         CK_CONFIG_MONO_ONLY},
105     {CK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
106         DEF_MESSAGE_JUSTIFY, Ck_Offset(Message, justify), 0},
107     {CK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
108         DEF_MESSAGE_TAKE_FOCUS, Ck_Offset(Message, takeFocus),
109         CK_CONFIG_NULL_OK},
110     {CK_CONFIG_STRING, "-text", "text", "Text",
111         DEF_MESSAGE_TEXT, Ck_Offset(Message, string), 0},
112     {CK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
113         DEF_MESSAGE_TEXT_VARIABLE, Ck_Offset(Message, textVarName),
114         CK_CONFIG_NULL_OK},
115     {CK_CONFIG_COORD, "-width", "width", "Width",
116         DEF_MESSAGE_WIDTH, Ck_Offset(Message, width), 0},
117     {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
118         (char *) NULL, 0, 0}
119 };
120
121 /*
122  * Forward declarations for procedures defined later in this file:
123  */
124
125 static void             MessageEventProc _ANSI_ARGS_((ClientData clientData,
126                             CkEvent *eventPtr));
127 static char *           MessageTextVarProc _ANSI_ARGS_((ClientData clientData,
128                             Tcl_Interp *interp, char *name1, char *name2,
129                             int flags));
130 static int              MessageWidgetCmd _ANSI_ARGS_((ClientData clientData,
131                             Tcl_Interp *interp, int argc, char **argv));
132 static void             MessageCmdDeletedProc _ANSI_ARGS_((
133                             ClientData clientData));
134 static void             ComputeMessageGeometry _ANSI_ARGS_((Message *msgPtr));
135 static int              ConfigureMessage _ANSI_ARGS_((Tcl_Interp *interp,
136                             Message *msgPtr, int argc, char **argv,
137                             int flags));
138 static void             DestroyMessage _ANSI_ARGS_((ClientData clientData));
139 static void             DisplayMessage _ANSI_ARGS_((ClientData clientData));
140 \f
141 /*
142  *--------------------------------------------------------------
143  *
144  * Ck_MessageCmd --
145  *
146  *      This procedure is invoked to process the "message" Tcl
147  *      command.  See the user documentation for details on what
148  *      it does.
149  *
150  * Results:
151  *      A standard Tcl result.
152  *
153  * Side effects:
154  *      See the user documentation.
155  *
156  *--------------------------------------------------------------
157  */
158
159 int
160 Ck_MessageCmd(clientData, interp, argc, argv)
161     ClientData clientData;      /* Main window associated with
162                                  * interpreter. */
163     Tcl_Interp *interp;         /* Current interpreter. */
164     int argc;                   /* Number of arguments. */
165     char **argv;                /* Argument strings. */
166 {
167     register Message *msgPtr;
168     CkWindow *new;
169     CkWindow *mainPtr = (CkWindow *) clientData;
170
171     if (argc < 2) {
172         Tcl_AppendResult(interp, "wrong # args:  should be \"",
173                 argv[0], " pathName ?options?\"", (char *) NULL);
174         return TCL_ERROR;
175     }
176
177     new = Ck_CreateWindowFromPath(interp, mainPtr, argv[1], 0);
178     if (new == NULL) {
179         return TCL_ERROR;
180     }
181
182     msgPtr = (Message *) ckalloc(sizeof (Message));
183     msgPtr->winPtr = new;
184     msgPtr->interp = interp;
185     msgPtr->widgetCmd = Tcl_CreateCommand(interp,
186         msgPtr->winPtr->pathName, MessageWidgetCmd,
187             (ClientData) msgPtr, MessageCmdDeletedProc);
188     msgPtr->string = NULL;
189     msgPtr->numChars = 0;
190     msgPtr->textVarName = NULL;
191     msgPtr->bg = 0;
192     msgPtr->fg = 0;
193     msgPtr->attr = 0;
194     msgPtr->anchor = CK_ANCHOR_CENTER;
195     msgPtr->width = 0;
196     msgPtr->aspect = 150;
197     msgPtr->lineLength = 0;
198     msgPtr->msgHeight = 0;
199     msgPtr->justify = CK_JUSTIFY_LEFT;
200     msgPtr->takeFocus = NULL;
201     msgPtr->flags = 0;
202
203     Ck_SetClass(msgPtr->winPtr, "Message");
204     Ck_CreateEventHandler(msgPtr->winPtr,
205         CK_EV_EXPOSE | CK_EV_MAP | CK_EV_DESTROY,
206         MessageEventProc, (ClientData) msgPtr);
207     if (ConfigureMessage(interp, msgPtr, argc-2, argv+2, 0) != TCL_OK) {
208         goto error;
209     }
210
211     interp->result = msgPtr->winPtr->pathName;
212     return TCL_OK;
213
214 error:
215     Ck_DestroyWindow(msgPtr->winPtr);
216     return TCL_ERROR;
217 }
218 \f
219 /*
220  *--------------------------------------------------------------
221  *
222  * MessageWidgetCmd --
223  *
224  *      This procedure is invoked to process the Tcl command
225  *      that corresponds to a widget managed by this module.
226  *      See the user documentation for details on what it does.
227  *
228  * Results:
229  *      A standard Tcl result.
230  *
231  * Side effects:
232  *      See the user documentation.
233  *
234  *--------------------------------------------------------------
235  */
236
237 static int
238 MessageWidgetCmd(clientData, interp, argc, argv)
239     ClientData clientData;      /* Information about message widget. */
240     Tcl_Interp *interp;         /* Current interpreter. */
241     int argc;                   /* Number of arguments. */
242     char **argv;                /* Argument strings. */
243 {
244     register Message *msgPtr = (Message *) clientData;
245     size_t length;
246     int c;
247
248     if (argc < 2) {
249         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
250                 " option ?arg arg ...?\"", (char *) NULL);
251         return TCL_ERROR;
252     }
253     c = argv[1][0];
254     length = strlen(argv[1]);
255     if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
256         && (length >= 2)) {
257         if (argc != 3) {
258             Tcl_AppendResult(interp, "wrong # args: should be \"",
259                     argv[0], " cget option\"",
260                     (char *) NULL);
261             return TCL_ERROR;
262         }
263         return Ck_ConfigureValue(interp, msgPtr->winPtr, configSpecs,
264                 (char *) msgPtr, argv[2], 0);
265     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
266             && (length  >= 2)) {
267         if (argc == 2) {
268             return Ck_ConfigureInfo(interp, msgPtr->winPtr, configSpecs,
269                     (char *) msgPtr, (char *) NULL, 0);
270         } else if (argc == 3) {
271             return Ck_ConfigureInfo(interp, msgPtr->winPtr, configSpecs,
272                     (char *) msgPtr, argv[2], 0);
273         } else {
274             return ConfigureMessage(interp, msgPtr, argc-2, argv+2,
275                     CK_CONFIG_ARGV_ONLY);
276         }
277     } else {
278         Tcl_AppendResult(interp, "bad option \"", argv[1],
279                 "\":  must be cget or configure", (char *) NULL);
280         return TCL_ERROR;
281     }
282 }
283 \f
284 /*
285  *----------------------------------------------------------------------
286  *
287  * DestroyMessage --
288  *
289  *      This procedure is invoked by Ck_EventuallyFree or Ck_Release
290  *      to clean up the internal structure of a message at a safe time
291  *      (when no-one is using it anymore).
292  *
293  * Results:
294  *      None.
295  *
296  * Side effects:
297  *      Everything associated with the message is freed up.
298  *
299  *----------------------------------------------------------------------
300  */
301
302 static void
303 DestroyMessage(clientData)
304     ClientData clientData;      /* Info about message widget. */
305 {
306     register Message *msgPtr = (Message *) clientData;
307
308     /*
309      * Free up all the stuff that requires special handling, then
310      * let Ck_FreeOptions handle all the standard option-related
311      * stuff.
312      */
313
314     if (msgPtr->textVarName != NULL) {
315         Tcl_UntraceVar(msgPtr->interp, msgPtr->textVarName,
316                 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
317                 MessageTextVarProc, (ClientData) msgPtr);
318     }
319     Ck_FreeOptions(configSpecs, (char *) msgPtr, 0);
320     ckfree((char *) msgPtr);
321 }
322 \f
323 /*
324  *----------------------------------------------------------------------
325  *
326  * MessageCmdDeletedProc --
327  *
328  *      This procedure is invoked when a widget command is deleted.  If
329  *      the widget isn't already in the process of being destroyed,
330  *      this command destroys it.
331  *
332  * Results:
333  *      None.
334  *
335  * Side effects:
336  *      The widget is destroyed.
337  *
338  *----------------------------------------------------------------------
339  */
340
341 static void
342 MessageCmdDeletedProc(clientData)
343     ClientData clientData;      /* Pointer to widget record for widget. */
344 {
345     Message *msgPtr = (Message *) clientData;
346     CkWindow *winPtr = msgPtr->winPtr;
347
348     /*
349      * This procedure could be invoked either because the window was
350      * destroyed and the command was then deleted (in which case winPtr
351      * is NULL) or because the command was deleted, and then this procedure
352      * destroys the widget.
353      */
354
355     if (winPtr != NULL) {
356         msgPtr->winPtr = NULL;
357         Ck_DestroyWindow(winPtr);
358     }
359 }
360 \f
361 /*
362  *----------------------------------------------------------------------
363  *
364  * ConfigureMessage --
365  *
366  *      This procedure is called to process an argv/argc list, plus
367  *      the option database, in order to configure (or
368  *      reconfigure) a message widget.
369  *
370  * Results:
371  *      The return value is a standard Tcl result.  If TCL_ERROR is
372  *      returned, then interp->result contains an error message.
373  *
374  * Side effects:
375  *      Configuration information, such as text string, colors,
376  *      etc. get set for msgPtr;  old resources get freed, if there
377  *      were any.
378  *
379  *----------------------------------------------------------------------
380  */
381
382 static int
383 ConfigureMessage(interp, msgPtr, argc, argv, flags)
384     Tcl_Interp *interp;         /* Used for error reporting. */
385     register Message *msgPtr;   /* Information about widget;  may or may
386                                  * not already have values for some fields. */
387     int argc;                   /* Number of valid entries in argv. */
388     char **argv;                /* Arguments. */
389     int flags;                  /* Flags to pass to Tk_ConfigureWidget. */
390 {
391     /*
392      * Eliminate any existing trace on a variable monitored by the message.
393      */
394
395     if (msgPtr->textVarName != NULL) {
396         Tcl_UntraceVar(interp, msgPtr->textVarName, 
397                 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
398                 MessageTextVarProc, (ClientData) msgPtr);
399     }
400
401     if (Ck_ConfigureWidget(interp, msgPtr->winPtr, configSpecs,
402             argc, argv, (char *) msgPtr, flags) != TCL_OK) {
403         return TCL_ERROR;
404     }
405
406     /*
407      * If the message is to display the value of a variable, then set up
408      * a trace on the variable's value, create the variable if it doesn't
409      * exist, and fetch its current value.
410      */
411
412     if (msgPtr->textVarName != NULL) {
413         char *value;
414
415         value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
416         if (value == NULL) {
417             Tcl_SetVar(interp, msgPtr->textVarName,
418                     msgPtr->string == NULL ? "" : msgPtr->string,
419                     TCL_GLOBAL_ONLY);
420         } else {
421             if (msgPtr->string != NULL) {
422                 ckfree(msgPtr->string);
423             }
424             msgPtr->string = (char *) ckalloc((unsigned) (strlen(value) + 1));
425             strcpy(msgPtr->string, value);
426         }
427         Tcl_TraceVar(interp, msgPtr->textVarName,
428                 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
429                 MessageTextVarProc, (ClientData) msgPtr);
430     }
431
432     /*
433      * A few other options need special processing, such as setting
434      * the background from a 3-D border or handling special defaults
435      * that couldn't be specified to Tk_ConfigureWidget.
436      */
437
438     if (msgPtr->string == NULL) {
439         msgPtr->string = ckalloc(1);
440         msgPtr->string[0] = '\0';
441     }
442     msgPtr->numChars = strlen(msgPtr->string);
443
444     /*
445      * Recompute the desired geometry for the window, and arrange for
446      * the window to be redisplayed.
447      */
448
449     ComputeMessageGeometry(msgPtr);
450     if ((msgPtr->winPtr != NULL) && (msgPtr->winPtr->flags & CK_MAPPED)
451             && !(msgPtr->flags & REDRAW_PENDING)) {
452         Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
453         msgPtr->flags |= REDRAW_PENDING;
454     }
455
456     return TCL_OK;
457 }
458 \f
459 /*
460  *--------------------------------------------------------------
461  *
462  * ComputeMessageGeometry --
463  *
464  *      Compute the desired geometry for a message window,
465  *      taking into account the desired aspect ratio for the
466  *      window.
467  *
468  * Results:
469  *      None.
470  *
471  * Side effects:
472  *      Ck_GeometryRequest is called to inform the geometry
473  *      manager of the desired geometry for this window.
474  *
475  *--------------------------------------------------------------
476  */
477
478 static void
479 ComputeMessageGeometry(msgPtr)
480     register Message *msgPtr;   /* Information about window. */
481 {
482     char *p;
483     int width, inc, height, numLines;
484     int thisWidth, maxWidth;
485     int aspect, lowerBound, upperBound, dummy;
486     CkWindow *winPtr = msgPtr->winPtr;
487
488     /*
489      * Compute acceptable bounds for the final aspect ratio.
490      */
491
492     aspect = msgPtr->aspect/10;
493     if (aspect < 5) {
494         aspect = 5;
495     }
496     lowerBound = msgPtr->aspect - aspect;
497     upperBound = msgPtr->aspect + aspect;
498
499     /*
500      * Do the computation in multiple passes:  start off with
501      * a very wide window, and compute its height.  Then change
502      * the width and try again.  Reduce the size of the change
503      * and iterate until dimensions are found that approximate
504      * the desired aspect ratio.  Or, if the user gave an explicit
505      * width then just use that.
506      */
507
508     if (msgPtr->width > 0) {
509         width = msgPtr->width;
510         inc = 0;
511     } else {
512         width = msgPtr->winPtr->mainPtr->winPtr->width;
513         inc = width/2;
514     }
515     for ( ; ; inc /= 2) {
516         maxWidth = 0;
517         for (numLines = 1, p = msgPtr->string; ; numLines++)  {
518             if (*p == '\n') {
519                 p++;
520                 continue;
521             }
522 #if CK_USE_UTF
523             CkMeasureChars(winPtr->mainPtr, p,
524                 msgPtr->numChars - (p - msgPtr->string),
525                 0, width, 0, CK_WHOLE_WORDS|CK_AT_LEAST_ONE, &thisWidth,
526                 &dummy);
527             p += dummy;
528 #else
529             p += CkMeasureChars(winPtr->mainPtr, p,
530                     msgPtr->numChars - (p - msgPtr->string),
531                     0, width, 0, CK_WHOLE_WORDS|CK_AT_LEAST_ONE, &thisWidth,
532                     &dummy);
533 #endif
534             if (thisWidth > maxWidth) {
535                 maxWidth = thisWidth;
536             }
537             if (*p == 0) {
538                 break;
539             }
540
541             /*
542              * Skip spaces and tabs at the beginning of a line, unless
543              * they follow a user-requested newline.
544              */
545
546             while (isspace((unsigned char) (*p))) {
547                 if (*p == '\n') {
548                     p++;
549                     break;
550                 }
551                 p++;
552             }
553         }
554
555         height = numLines;
556         if (inc <= 2) {
557             break;
558         }
559         aspect = (100 * maxWidth) / height;
560         if (aspect < lowerBound) {
561             width += inc;
562         } else if (aspect > upperBound) {
563             width -= inc;
564         } else {
565             break;
566         }
567     }
568     msgPtr->lineLength = maxWidth;
569     msgPtr->msgHeight = numLines;
570     Ck_GeometryRequest(msgPtr->winPtr, maxWidth, height);
571 }
572 \f
573 /*
574  *--------------------------------------------------------------
575  *
576  * DisplayMessage --
577  *
578  *      This procedure redraws the contents of a message window.
579  *
580  * Results:
581  *      None.
582  *
583  * Side effects:
584  *      Information appears on the screen.
585  *
586  *--------------------------------------------------------------
587  */
588
589 static void
590 DisplayMessage(clientData)
591     ClientData clientData;      /* Information about window. */
592 {
593     register Message *msgPtr = (Message *) clientData;
594     register CkWindow *winPtr = msgPtr->winPtr;
595     char *p;
596     int x, y, lineLength, numChars, charsLeft, dummy;
597
598     msgPtr->flags &= ~REDRAW_PENDING;
599     if (msgPtr->winPtr == NULL || !(winPtr->flags & CK_MAPPED)) {
600         return;
601     }
602
603     Ck_SetWindowAttr(winPtr, msgPtr->fg, msgPtr->bg, msgPtr->attr);
604     Ck_ClearToBot(winPtr, 0, 0);
605
606     /*
607      * Compute starting y-location for message based on message size
608      * and anchor option.
609      */
610
611     switch (msgPtr->anchor) {
612         case CK_ANCHOR_NW: case CK_ANCHOR_N: case CK_ANCHOR_NE:
613             y = 0;
614             break;
615         case CK_ANCHOR_W: case CK_ANCHOR_CENTER: case CK_ANCHOR_E:
616             y = (winPtr->height - msgPtr->msgHeight) / 2;
617             break;
618         default:
619             y = winPtr->height - msgPtr->msgHeight;
620             break;
621     }
622     if (y < 0) {
623         y = 0;
624     }
625
626     /*
627      * Work through the string to display one line at a time.
628      * Display each line in three steps.  First compute the
629      * line's width, then figure out where to display the
630      * line to justify it properly, then display the line.
631      */
632
633     for (p = msgPtr->string, charsLeft = msgPtr->numChars; *p != 0; y++) {
634         if (*p == '\n') {
635             p++;
636             charsLeft--;
637             continue;
638         }
639         numChars = CkMeasureChars(winPtr->mainPtr, p, charsLeft, 0,
640                 msgPtr->lineLength,
641                 0, CK_WHOLE_WORDS | CK_AT_LEAST_ONE, &lineLength, &dummy);
642         switch (msgPtr->anchor) {
643             case CK_ANCHOR_NW: case CK_ANCHOR_W: case CK_ANCHOR_SW:
644                 x = 0;
645                 break;
646             case CK_ANCHOR_N: case CK_ANCHOR_CENTER: case CK_ANCHOR_S:
647                 x = (winPtr->width - msgPtr->lineLength) / 2;
648                 break;
649             default:
650                 x = winPtr->width - msgPtr->lineLength;
651                 break;
652         }
653         if (msgPtr->justify == CK_JUSTIFY_CENTER) {
654             x += (msgPtr->lineLength - lineLength) / 2;
655         } else if (msgPtr->justify == CK_JUSTIFY_RIGHT) {
656             x += msgPtr->lineLength - lineLength;
657         }
658 #if 0
659         if (x < 0) {
660             x = 0;
661         }
662 #endif
663         CkDisplayChars(winPtr->mainPtr, winPtr->window, p, numChars,
664             x, y, x, 0);
665         p += numChars;
666         charsLeft -= numChars;
667
668         /*
669          * Skip blanks at the beginning of a line, unless they follow
670          * a user-requested newline.
671          */
672
673         while (isspace((unsigned char) (*p))) {
674             charsLeft--;
675             if (*p == '\n') {
676                 p++;
677                 break;
678             }
679             p++;
680         }
681     }
682     Ck_EventuallyRefresh(winPtr);
683 }
684 \f
685 /*
686  *--------------------------------------------------------------
687  *
688  * MessageEventProc --
689  *
690  *      This procedure is invoked by the Tk dispatcher for various
691  *      events on messages.
692  *
693  * Results:
694  *      None.
695  *
696  * Side effects:
697  *      When the window gets deleted, internal structures get
698  *      cleaned up.  When it gets exposed, it is redisplayed.
699  *
700  *--------------------------------------------------------------
701  */
702
703 static void
704 MessageEventProc(clientData, eventPtr)
705     ClientData clientData;      /* Information about window. */
706     CkEvent *eventPtr;          /* Information about event. */
707 {
708     Message *msgPtr = (Message *) clientData;
709
710     if (eventPtr->type == CK_EV_EXPOSE) {
711         if (msgPtr->winPtr != NULL && !(msgPtr->flags & REDRAW_PENDING)) {
712             Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
713             msgPtr->flags |= REDRAW_PENDING;
714         }
715     } else if (eventPtr->type == CK_EV_DESTROY) {
716         if (msgPtr->winPtr != NULL) {
717             msgPtr->winPtr = NULL;
718             Tcl_DeleteCommand(msgPtr->interp,
719                     Tcl_GetCommandName(msgPtr->interp, msgPtr->widgetCmd));
720         }
721         if (msgPtr->flags & REDRAW_PENDING) {
722             Tk_CancelIdleCall(DisplayMessage, (ClientData) msgPtr);
723         }
724         Ck_EventuallyFree((ClientData) msgPtr, (Ck_FreeProc *) DestroyMessage);
725     }
726 }
727 \f
728 /*
729  *--------------------------------------------------------------
730  *
731  * MessageTextVarProc --
732  *
733  *      This procedure is invoked when someone changes the variable
734  *      whose contents are to be displayed in a message.
735  *
736  * Results:
737  *      NULL is always returned.
738  *
739  * Side effects:
740  *      The text displayed in the message will change to match the
741  *      variable.
742  *
743  *--------------------------------------------------------------
744  */
745
746 static char *
747 MessageTextVarProc(clientData, interp, name1, name2, flags)
748     ClientData clientData;      /* Information about message. */
749     Tcl_Interp *interp;         /* Interpreter containing variable. */
750     char *name1;                /* Name of variable. */
751     char *name2;                /* Second part of variable name. */
752     int flags;                  /* Information about what happened. */
753 {
754     register Message *msgPtr = (Message *) clientData;
755     char *value;
756
757     /*
758      * If the variable is unset, then immediately recreate it unless
759      * the whole interpreter is going away.
760      */
761
762     if (flags & TCL_TRACE_UNSETS) {
763         if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
764             Tcl_SetVar(interp, msgPtr->textVarName,
765                     msgPtr->string == NULL ? "" : msgPtr->string,
766                     TCL_GLOBAL_ONLY);
767             Tcl_TraceVar(interp, msgPtr->textVarName,
768                     TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
769                     MessageTextVarProc, clientData);
770         }
771         return (char *) NULL;
772     }
773
774     value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
775     if (value == NULL) {
776         value = "";
777     }
778     if (msgPtr->string != NULL) {
779         ckfree(msgPtr->string);
780     }
781     msgPtr->numChars = strlen(value);
782     msgPtr->string = (char *) ckalloc((unsigned) (msgPtr->numChars + 1));
783     strcpy(msgPtr->string, value);
784     ComputeMessageGeometry(msgPtr);
785
786     if ((msgPtr->winPtr != NULL) && (msgPtr->winPtr->flags & CK_MAPPED)
787             && !(msgPtr->flags & REDRAW_PENDING)) {
788         Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
789         msgPtr->flags |= REDRAW_PENDING;
790     }
791     return (char *) NULL;
792 }