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 (c) 1990-1994 The Regents of the University of California.
9 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
10 * Copyright (c) 1998-2000 by 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 * RCS: @(#) $Id$
16 */
17
18#include "default.h"
19#include "tkInt.h"
20
21/*
22 * A data structure of the following type is kept for each message widget
23 * managed by this file:
24 */
25
26typedef 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    Tk_OptionTable optionTable;	/* Table that defines options available for
32				 * this widget. */
33    Display *display;		/* Display containing widget. Used, among
34				 * other things, so that resources can be
35				 * freed even after tkwin has gone away. */
36    Tcl_Interp *interp;		/* Interpreter associated with message. */
37    Tcl_Command widgetCmd;	/* Token for message's widget command. */
38
39    /*
40     * Information used when displaying widget:
41     */
42
43    char *string;		/* String displayed in message. */
44    int numChars;		/* Number of characters in string, not
45				 * including terminating NULL. */
46    char *textVarName;		/* Name of variable (malloc'ed) or NULL.
47				 * If non-NULL, message displays the contents
48				 * of this variable. */
49    Tk_3DBorder border;		/* Structure used to draw 3-D border and
50				 * background. NULL means a border hasn't been
51				 * created yet. */
52    int borderWidth;		/* Width of border. */
53    int relief;			/* 3-D effect: TK_RELIEF_RAISED, etc. */
54    int highlightWidth;		/* Width in pixels of highlight to draw
55				 * around widget when it has the focus.
56				 * <= 0 means don't draw a highlight. */
57    XColor *highlightBgColorPtr;
58				/* Color for drawing traversal highlight
59				 * area when highlight is off. */
60    XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
61    Tk_Font tkfont;		/* Information about text font, or NULL. */
62    XColor *fgColorPtr;		/* Foreground color in normal mode. */
63    Tcl_Obj *padXPtr, *padYPtr;	/* Tcl_Obj rep's of padX, padY values. */
64    int padX, padY;		/* User-requested extra space around text. */
65    int width;			/* User-requested width, in pixels. 0 means
66				 * compute width using aspect ratio below. */
67    int aspect;			/* Desired aspect ratio for window
68				 * (100*width/height). */
69    int msgWidth;		/* Width in pixels needed to display
70				 * message. */
71    int msgHeight;		/* Height in pixels needed to display
72				 * message. */
73    Tk_Anchor anchor;		/* Where to position text within window region
74				 * if window is larger or smaller than
75				 * needed. */
76    Tk_Justify justify;		/* Justification for text. */
77
78    GC textGC;			/* GC for drawing text in normal mode. */
79    Tk_TextLayout textLayout;	/* Saved layout information. */
80
81    /*
82     * Miscellaneous information:
83     */
84
85    Tk_Cursor cursor;		/* Current cursor for window, or None. */
86    char *takeFocus;		/* Value of -takefocus option; not used in the
87				 * C code, but used by keyboard traversal
88				 * scripts. Malloc'ed, but may be NULL. */
89    int flags;			/* Various flags; see below for
90				 * definitions. */
91} Message;
92
93/*
94 * Flag bits for messages:
95 *
96 * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
97 *				has already been queued to redraw
98 *				this window.
99 * GOT_FOCUS:			Non-zero means this button currently
100 *				has the input focus.
101 * MESSAGE_DELETED:		The message has been effectively deleted.
102 */
103
104#define REDRAW_PENDING		1
105#define GOT_FOCUS		4
106#define MESSAGE_DELETED		8
107
108/*
109 * Information used for argv parsing.
110 */
111
112static const Tk_OptionSpec optionSpecs[] = {
113    {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", DEF_MESSAGE_ANCHOR,
114	 -1, Tk_Offset(Message, anchor), 0, 0, 0},
115    {TK_OPTION_INT, "-aspect", "aspect", "Aspect", DEF_MESSAGE_ASPECT,
116	 -1, Tk_Offset(Message, aspect), 0, 0, 0},
117    {TK_OPTION_BORDER, "-background", "background", "Background",
118	 DEF_MESSAGE_BG_COLOR, -1, Tk_Offset(Message, border), 0,
119	 (ClientData) DEF_MESSAGE_BG_MONO, 0},
120    {TK_OPTION_SYNONYM, "-bd", NULL, NULL, NULL,
121	 0, -1, 0, (ClientData) "-borderwidth", 0},
122    {TK_OPTION_SYNONYM, "-bg", NULL, NULL, NULL,
123	 0, -1, 0, (ClientData) "-background", 0},
124    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
125	 DEF_MESSAGE_BORDER_WIDTH, -1,
126	 Tk_Offset(Message, borderWidth), 0, 0, 0},
127    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
128	 DEF_MESSAGE_CURSOR, -1, Tk_Offset(Message, cursor),
129	 TK_OPTION_NULL_OK, 0, 0},
130    {TK_OPTION_SYNONYM, "-fg", NULL, NULL, NULL,
131	 0, -1, 0, (ClientData) "-foreground", 0},
132    {TK_OPTION_FONT, "-font", "font", "Font",
133	DEF_MESSAGE_FONT, -1, Tk_Offset(Message, tkfont), 0, 0, 0},
134    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
135	DEF_MESSAGE_FG, -1, Tk_Offset(Message, fgColorPtr), 0, 0, 0},
136    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
137	 "HighlightBackground", DEF_MESSAGE_HIGHLIGHT_BG, -1,
138	 Tk_Offset(Message, highlightBgColorPtr), 0, 0},
139    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
140	 DEF_MESSAGE_HIGHLIGHT, -1, Tk_Offset(Message, highlightColorPtr),
141	 0, 0, 0},
142    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
143	"HighlightThickness", DEF_MESSAGE_HIGHLIGHT_WIDTH, -1,
144	 Tk_Offset(Message, highlightWidth), 0, 0, 0},
145    {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
146	DEF_MESSAGE_JUSTIFY, -1, Tk_Offset(Message, justify), 0, 0, 0},
147    {TK_OPTION_PIXELS, "-padx", "padX", "Pad",
148	 DEF_MESSAGE_PADX, Tk_Offset(Message, padXPtr),
149	 Tk_Offset(Message, padX), 0, 0, 0},
150    {TK_OPTION_PIXELS, "-pady", "padY", "Pad",
151	 DEF_MESSAGE_PADY, Tk_Offset(Message, padYPtr),
152	 Tk_Offset(Message, padY), 0, 0, 0},
153    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
154	DEF_MESSAGE_RELIEF, -1, Tk_Offset(Message, relief), 0, 0, 0},
155    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
156	DEF_MESSAGE_TAKE_FOCUS, -1, Tk_Offset(Message, takeFocus),
157	TK_OPTION_NULL_OK, 0, 0},
158    {TK_OPTION_STRING, "-text", "text", "Text",
159	DEF_MESSAGE_TEXT, -1, Tk_Offset(Message, string), 0, 0, 0},
160    {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
161	DEF_MESSAGE_TEXT_VARIABLE, -1, Tk_Offset(Message, textVarName),
162	TK_OPTION_NULL_OK, 0, 0},
163    {TK_OPTION_PIXELS, "-width", "width", "Width",
164	DEF_MESSAGE_WIDTH, -1, Tk_Offset(Message, width), 0, 0 ,0},
165    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0}
166};
167
168/*
169 * Forward declarations for functions defined later in this file:
170 */
171
172static void		MessageCmdDeletedProc(ClientData clientData);
173static void		MessageEventProc(ClientData clientData,
174			    XEvent *eventPtr);
175static char *		MessageTextVarProc(ClientData clientData,
176			    Tcl_Interp *interp, CONST char *name1,
177			    CONST char *name2, int flags);
178static int		MessageWidgetObjCmd(ClientData clientData,
179			    Tcl_Interp *interp, int objc,
180			    Tcl_Obj *CONST objv[]);
181static void		MessageWorldChanged(ClientData instanceData);
182static void		ComputeMessageGeometry(Message *msgPtr);
183static int		ConfigureMessage(Tcl_Interp *interp, Message *msgPtr,
184			    int objc, Tcl_Obj *CONST objv[], int flags);
185static void		DestroyMessage(char *memPtr);
186static void		DisplayMessage(ClientData clientData);
187
188/*
189 * The structure below defines message class behavior by means of functions
190 * that can be invoked from generic window code.
191 */
192
193static Tk_ClassProcs messageClass = {
194    sizeof(Tk_ClassProcs),	/* size */
195    MessageWorldChanged,	/* worldChangedProc */
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
215int
216Tk_MessageObjCmd(
217    ClientData clientData,	/* NULL. */
218    Tcl_Interp *interp,		/* Current interpreter. */
219    int objc,			/* Number of arguments. */
220    Tcl_Obj *CONST objv[])	/* Argument strings. */
221{
222    register Message *msgPtr;
223    Tk_OptionTable optionTable;
224    Tk_Window tkwin;
225
226    if (objc < 2) {
227	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
228	return TCL_ERROR;
229    }
230
231    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
232	    Tcl_GetString(objv[1]), NULL);
233    if (tkwin == NULL) {
234	return TCL_ERROR;
235    }
236
237    /*
238     * Create the option table for this widget class. If it has already been
239     * created, the cached pointer will be returned.
240     */
241
242    optionTable = Tk_CreateOptionTable(interp, optionSpecs);
243
244    msgPtr = (Message *) ckalloc(sizeof(Message));
245    memset(msgPtr, 0, (size_t) sizeof(Message));
246
247    /*
248     * Set values for those fields that don't take a 0 or NULL value.
249     */
250
251    msgPtr->tkwin = tkwin;
252    msgPtr->display = Tk_Display(tkwin);
253    msgPtr->interp = interp;
254    msgPtr->widgetCmd = Tcl_CreateObjCommand(interp,
255	    Tk_PathName(msgPtr->tkwin), MessageWidgetObjCmd,
256	    (ClientData) msgPtr, MessageCmdDeletedProc);
257    msgPtr->optionTable = optionTable;
258    msgPtr->relief = TK_RELIEF_FLAT;
259    msgPtr->textGC = None;
260    msgPtr->anchor = TK_ANCHOR_CENTER;
261    msgPtr->aspect = 150;
262    msgPtr->justify = TK_JUSTIFY_LEFT;
263    msgPtr->cursor = None;
264
265    Tk_SetClass(msgPtr->tkwin, "Message");
266    Tk_SetClassProcs(msgPtr->tkwin, &messageClass, (ClientData) msgPtr);
267    Tk_CreateEventHandler(msgPtr->tkwin,
268	    ExposureMask|StructureNotifyMask|FocusChangeMask,
269	    MessageEventProc, (ClientData) msgPtr);
270    if (Tk_InitOptions(interp, (char *)msgPtr, optionTable, tkwin) != TCL_OK) {
271	Tk_DestroyWindow(msgPtr->tkwin);
272	return TCL_ERROR;
273    }
274
275    if (ConfigureMessage(interp, msgPtr, objc-2, objv+2, 0) != TCL_OK) {
276	Tk_DestroyWindow(msgPtr->tkwin);
277	return TCL_ERROR;
278    }
279
280    Tcl_SetResult(interp, Tk_PathName(msgPtr->tkwin), TCL_STATIC);
281    return TCL_OK;
282}
283
284/*
285 *--------------------------------------------------------------
286 *
287 * MessageWidgetObjCmd --
288 *
289 *	This function is invoked to process the Tcl command that corresponds
290 *	to a widget managed by this module. See the user documentation for
291 *	details on what it does.
292 *
293 * Results:
294 *	A standard Tcl result.
295 *
296 * Side effects:
297 *	See the user documentation.
298 *
299 *--------------------------------------------------------------
300 */
301
302static int
303MessageWidgetObjCmd(
304    ClientData clientData,	/* Information about message widget. */
305    Tcl_Interp *interp,		/* Current interpreter. */
306    int objc,			/* Number of arguments. */
307    Tcl_Obj *CONST objv[])	/* Argument strings. */
308{
309    register Message *msgPtr = (Message *) clientData;
310    static CONST char *optionStrings[] = { "cget", "configure", NULL };
311    enum options { MESSAGE_CGET, MESSAGE_CONFIGURE };
312    int index;
313    int result = TCL_OK;
314    Tcl_Obj *objPtr;
315
316    if (objc < 2) {
317	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
318	return TCL_ERROR;
319    }
320
321    if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
322	    &index) != TCL_OK) {
323	return TCL_ERROR;
324    }
325
326    Tcl_Preserve((ClientData) msgPtr);
327
328    switch ((enum options) index) {
329    case MESSAGE_CGET:
330	if (objc != 3) {
331	    Tcl_WrongNumArgs(interp, 2, objv, "option");
332	    result = TCL_ERROR;
333	} else {
334	    objPtr = Tk_GetOptionValue(interp, (char *) msgPtr,
335		    msgPtr->optionTable, objv[2], msgPtr->tkwin);
336	    if (objPtr == NULL) {
337		result = TCL_ERROR;
338	    } else {
339		Tcl_SetObjResult(interp, objPtr);
340		result = TCL_OK;
341	    }
342	}
343	break;
344    case MESSAGE_CONFIGURE:
345	if (objc <= 3) {
346	    objPtr = Tk_GetOptionInfo(interp, (char *) msgPtr,
347		    msgPtr->optionTable, (objc == 3) ? objv[2] : NULL,
348		    msgPtr->tkwin);
349	    if (objPtr == NULL) {
350		result = TCL_ERROR;
351	    } else {
352		Tcl_SetObjResult(interp, objPtr);
353		result = TCL_OK;
354	    }
355	} else {
356	    result = ConfigureMessage(interp, msgPtr, objc-2, objv+2, 0);
357	}
358	break;
359    }
360
361    Tcl_Release((ClientData) msgPtr);
362    return result;
363}
364
365/*
366 *----------------------------------------------------------------------
367 *
368 * DestroyMessage --
369 *
370 *	This function is invoked by Tcl_EventuallyFree or Tcl_Release to clean
371 *	up the internal structure of a message at a safe time (when no-one is
372 *	using it anymore).
373 *
374 * Results:
375 *	None.
376 *
377 * Side effects:
378 *	Everything associated with the message is freed up.
379 *
380 *----------------------------------------------------------------------
381 */
382
383static void
384DestroyMessage(
385    char *memPtr)		/* Info about message widget. */
386{
387    register Message *msgPtr = (Message *) memPtr;
388
389    msgPtr->flags |= MESSAGE_DELETED;
390
391    Tcl_DeleteCommandFromToken(msgPtr->interp, msgPtr->widgetCmd);
392    if (msgPtr->flags & REDRAW_PENDING) {
393	Tcl_CancelIdleCall(DisplayMessage, (ClientData) msgPtr);
394    }
395
396    /*
397     * Free up all the stuff that requires special handling, then let
398     * Tk_FreeConfigOptions handle all the standard option-related stuff.
399     */
400
401    if (msgPtr->textGC != None) {
402	Tk_FreeGC(msgPtr->display, msgPtr->textGC);
403    }
404    if (msgPtr->textLayout != NULL) {
405	Tk_FreeTextLayout(msgPtr->textLayout);
406    }
407    if (msgPtr->textVarName != NULL) {
408	Tcl_UntraceVar(msgPtr->interp, msgPtr->textVarName,
409		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
410		MessageTextVarProc, (ClientData) msgPtr);
411    }
412    Tk_FreeConfigOptions((char *) msgPtr, msgPtr->optionTable, msgPtr->tkwin);
413    msgPtr->tkwin = NULL;
414    ckfree((char *) msgPtr);
415}
416
417/*
418 *----------------------------------------------------------------------
419 *
420 * ConfigureMessage --
421 *
422 *	This function is called to process an argv/argc list, plus the Tk
423 *	option database, in order to configure (or reconfigure) a message
424 *	widget.
425 *
426 * Results:
427 *	The return value is a standard Tcl result. If TCL_ERROR is returned,
428 *	then the interp's result contains an error message.
429 *
430 * Side effects:
431 *	Configuration information, such as text string, colors, font, etc. get
432 *	set for msgPtr; old resources get freed, if there were any.
433 *
434 *----------------------------------------------------------------------
435 */
436
437static int
438ConfigureMessage(
439    Tcl_Interp *interp,		/* Used for error reporting. */
440    register Message *msgPtr,	/* Information about widget; may or may not
441				 * already have values for some fields. */
442    int objc,			/* Number of valid entries in argv. */
443    Tcl_Obj *CONST objv[],	/* Arguments. */
444    int flags)			/* Flags to pass to Tk_ConfigureWidget. */
445{
446    Tk_SavedOptions savedOptions;
447
448    /*
449     * Eliminate any existing trace on a variable monitored by the message.
450     */
451
452    if (msgPtr->textVarName != NULL) {
453	Tcl_UntraceVar(interp, msgPtr->textVarName,
454		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
455		MessageTextVarProc, (ClientData) msgPtr);
456    }
457
458    if (Tk_SetOptions(interp, (char *) msgPtr, msgPtr->optionTable, objc, objv,
459	    msgPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
460	Tk_RestoreSavedOptions(&savedOptions);
461	return TCL_ERROR;
462    }
463
464    /*
465     * If the message is to display the value of a variable, then set up a
466     * trace on the variable's value, create the variable if it doesn't exist,
467     * and fetch its current value.
468     */
469
470    if (msgPtr->textVarName != NULL) {
471	CONST char *value;
472
473	value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
474	if (value == NULL) {
475	    Tcl_SetVar(interp, msgPtr->textVarName, msgPtr->string,
476		    TCL_GLOBAL_ONLY);
477	} else {
478	    if (msgPtr->string != NULL) {
479		ckfree(msgPtr->string);
480	    }
481	    msgPtr->string = strcpy(ckalloc(strlen(value) + 1), value);
482	}
483	Tcl_TraceVar(interp, msgPtr->textVarName,
484		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
485		MessageTextVarProc, (ClientData) msgPtr);
486    }
487
488    /*
489     * A few other options need special processing, such as setting the
490     * background from a 3-D border or handling special defaults that couldn't
491     * be specified to Tk_ConfigureWidget.
492     */
493
494    msgPtr->numChars = Tcl_NumUtfChars(msgPtr->string, -1);
495
496    if (msgPtr->highlightWidth < 0) {
497	msgPtr->highlightWidth = 0;
498    }
499
500    Tk_FreeSavedOptions(&savedOptions);
501    MessageWorldChanged((ClientData) msgPtr);
502    return TCL_OK;
503}
504
505/*
506 *---------------------------------------------------------------------------
507 *
508 * MessageWorldChanged --
509 *
510 *	This function is called when the world has changed in some way and the
511 *	widget needs to recompute all its graphics contexts and determine its
512 *	new geometry.
513 *
514 * Results:
515 *	None.
516 *
517 * Side effects:
518 *	Message will be relayed out and redisplayed.
519 *
520 *---------------------------------------------------------------------------
521 */
522
523static void
524MessageWorldChanged(
525    ClientData instanceData)	/* Information about widget. */
526{
527    XGCValues gcValues;
528    GC gc = None;
529    Tk_FontMetrics fm;
530    Message *msgPtr;
531
532    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 != None) {
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, (ClientData) 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
585static void
586ComputeMessageGeometry(
587    register 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
667static void
668DisplayMessage(
669    ClientData clientData)	/* Information about window. */
670{
671    register Message *msgPtr = (Message *) clientData;
672    register 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
742static void
743MessageEventProc(
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((char *) 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, (ClientData) 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
796static void
797MessageCmdDeletedProc(
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	/* ARGSUSED */
832static char *
833MessageTextVarProc(
834    ClientData clientData,	/* Information about message. */
835    Tcl_Interp *interp,		/* Interpreter containing variable. */
836    CONST char *name1,		/* Name of variable. */
837    CONST char *name2,		/* Second part of variable name. */
838    int flags)			/* Information about what happened. */
839{
840    register Message *msgPtr = (Message *) clientData;
841    CONST char *value;
842
843    /*
844     * If the variable is unset, then immediately recreate it unless the whole
845     * interpreter is going away.
846     */
847
848    if (flags & TCL_TRACE_UNSETS) {
849	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
850	    Tcl_SetVar(interp, msgPtr->textVarName, msgPtr->string,
851		    TCL_GLOBAL_ONLY);
852	    Tcl_TraceVar(interp, msgPtr->textVarName,
853		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
854		    MessageTextVarProc, clientData);
855	}
856	return NULL;
857    }
858
859    value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
860    if (value == NULL) {
861	value = "";
862    }
863    if (msgPtr->string != NULL) {
864	ckfree(msgPtr->string);
865    }
866    msgPtr->numChars = Tcl_NumUtfChars(value, -1);
867    msgPtr->string = (char *) ckalloc((unsigned) (strlen(value) + 1));
868    strcpy(msgPtr->string, value);
869    ComputeMessageGeometry(msgPtr);
870
871    if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
872	    && !(msgPtr->flags & REDRAW_PENDING)) {
873	Tcl_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
874	msgPtr->flags |= REDRAW_PENDING;
875    }
876    return NULL;
877}
878
879/*
880 * Local Variables:
881 * mode: c
882 * c-basic-offset: 4
883 * fill-column: 78
884 * End:
885 */
886