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