]>
Commit | Line | Data |
---|---|---|
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 1990 Regents of the University of California. | |
9 | * Permission to use, copy, modify, and distribute this | |
10 | * software and its documentation for any purpose and without | |
11 | * fee is hereby granted, provided that the above copyright | |
12 | * notice appear in all copies. The University of California | |
13 | * makes no representations about the suitability of this | |
14 | * software for any purpose. It is provided "as is" without | |
15 | * express or implied warranty. | |
16 | */ | |
17 | ||
18 | #ifndef lint | |
19 | static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMessage.c,v 1.36 92/06/08 11:06:05 ouster Exp $ SPRITE (Berkeley)"; | |
20 | #endif | |
21 | ||
22 | #include "tkconfig.h" | |
23 | #include "default.h" | |
24 | #include "tkint.h" | |
25 | ||
26 | /* | |
27 | * A data structure of the following type is kept for each message | |
28 | * widget managed by this file: | |
29 | */ | |
30 | ||
31 | typedef struct { | |
32 | Tk_Window tkwin; /* Window that embodies the message. NULL | |
33 | * means that the window has been destroyed | |
34 | * but the data structures haven't yet been | |
35 | * cleaned up.*/ | |
36 | Tcl_Interp *interp; /* Interpreter associated with message. */ | |
37 | Tk_Uid string; /* String displayed in message. */ | |
38 | int numChars; /* Number of characters in string, not | |
39 | * including terminating NULL character. */ | |
40 | char *textVarName; /* Name of variable (malloc'ed) or NULL. | |
41 | * If non-NULL, message displays the contents | |
42 | * of this variable. */ | |
43 | ||
44 | /* | |
45 | * Information used when displaying widget: | |
46 | */ | |
47 | ||
48 | Tk_3DBorder border; /* Structure used to draw 3-D border and | |
49 | * background. NULL means a border hasn't | |
50 | * been created yet. */ | |
51 | int borderWidth; /* Width of border. */ | |
52 | int relief; /* 3-D effect: TK_RELIEF_RAISED, etc. */ | |
53 | XFontStruct *fontPtr; /* Information about text font, or NULL. */ | |
54 | XColor *fgColorPtr; /* Foreground color in normal mode. */ | |
55 | GC textGC; /* GC for drawing text in normal mode. */ | |
56 | int padX, padY; /* User-requested extra space around text. */ | |
57 | Tk_Anchor anchor; /* Where to position text within window region | |
58 | * if window is larger or smaller than | |
59 | * needed. */ | |
60 | int width; /* User-requested width, in pixels. 0 means | |
61 | * compute width using aspect ratio below. */ | |
62 | int aspect; /* Desired aspect ratio for window | |
63 | * (100*width/height). */ | |
64 | int lineLength; /* Length of each line, in pixels. Computed | |
65 | * from width and/or aspect. */ | |
66 | int msgHeight; /* Total number of pixels in vertical direction | |
67 | * needed to display message. */ | |
68 | Tk_Justify justify; /* Justification for text. */ | |
69 | ||
70 | /* | |
71 | * Miscellaneous information: | |
72 | */ | |
73 | ||
74 | Cursor cursor; /* Current cursor for window, or None. */ | |
75 | int flags; /* Various flags; see below for | |
76 | * definitions. */ | |
77 | } Message; | |
78 | ||
79 | /* | |
80 | * Flag bits for messages: | |
81 | * | |
82 | * REDRAW_PENDING: Non-zero means a DoWhenIdle handler | |
83 | * has already been queued to redraw | |
84 | * this window. | |
85 | * CLEAR_NEEDED; Need to clear the window when redrawing. | |
86 | */ | |
87 | ||
88 | #define REDRAW_PENDING 1 | |
89 | #define CLEAR_NEEDED 2 | |
90 | ||
91 | /* | |
92 | * Information used for argv parsing. | |
93 | */ | |
94 | ||
95 | ||
96 | static Tk_ConfigSpec configSpecs[] = { | |
97 | {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", | |
98 | DEF_MESSAGE_ANCHOR, Tk_Offset(Message, anchor), 0}, | |
99 | {TK_CONFIG_INT, "-aspect", "aspect", "Aspect", | |
100 | DEF_MESSAGE_ASPECT, Tk_Offset(Message, aspect), 0}, | |
101 | {TK_CONFIG_BORDER, "-background", "background", "Background", | |
102 | DEF_MESSAGE_BG_COLOR, Tk_Offset(Message, border), | |
103 | TK_CONFIG_COLOR_ONLY}, | |
104 | {TK_CONFIG_BORDER, "-background", "background", "Background", | |
105 | DEF_MESSAGE_BG_MONO, Tk_Offset(Message, border), | |
106 | TK_CONFIG_MONO_ONLY}, | |
107 | {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, | |
108 | (char *) NULL, 0, 0}, | |
109 | {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL, | |
110 | (char *) NULL, 0, 0}, | |
111 | {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", | |
112 | DEF_MESSAGE_BORDER_WIDTH, Tk_Offset(Message, borderWidth), 0}, | |
113 | {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", | |
114 | DEF_MESSAGE_CURSOR, Tk_Offset(Message, cursor), TK_CONFIG_NULL_OK}, | |
115 | {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL, | |
116 | (char *) NULL, 0, 0}, | |
117 | {TK_CONFIG_FONT, "-font", "font", "Font", | |
118 | DEF_MESSAGE_FONT, Tk_Offset(Message, fontPtr), 0}, | |
119 | {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", | |
120 | DEF_MESSAGE_FG, Tk_Offset(Message, fgColorPtr), 0}, | |
121 | {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify", | |
122 | DEF_MESSAGE_JUSTIFY, Tk_Offset(Message, justify), 0}, | |
123 | {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", | |
124 | DEF_MESSAGE_PADX, Tk_Offset(Message, padX), 0}, | |
125 | {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", | |
126 | DEF_MESSAGE_PADY, Tk_Offset(Message, padY), 0}, | |
127 | {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", | |
128 | DEF_MESSAGE_RELIEF, Tk_Offset(Message, relief), 0}, | |
129 | {TK_CONFIG_STRING, "-text", "text", "Text", | |
130 | DEF_MESSAGE_TEXT, Tk_Offset(Message, string), 0}, | |
131 | {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable", | |
132 | DEF_MESSAGE_TEXT_VARIABLE, Tk_Offset(Message, textVarName), | |
133 | TK_CONFIG_NULL_OK}, | |
134 | {TK_CONFIG_PIXELS, "-width", "width", "Width", | |
135 | DEF_MESSAGE_WIDTH, Tk_Offset(Message, width), 0}, | |
136 | {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, | |
137 | (char *) NULL, 0, 0} | |
138 | }; | |
139 | ||
140 | /* | |
141 | * Forward declarations for procedures defined later in this file: | |
142 | */ | |
143 | ||
144 | static void MessageEventProc _ANSI_ARGS_((ClientData clientData, | |
145 | XEvent *eventPtr)); | |
146 | static char * MessageTextVarProc _ANSI_ARGS_((ClientData clientData, | |
147 | Tcl_Interp *interp, char *name1, char *name2, | |
148 | int flags)); | |
149 | static int MessageWidgetCmd _ANSI_ARGS_((ClientData clientData, | |
150 | Tcl_Interp *interp, int argc, char **argv)); | |
151 | static void ComputeMessageGeometry _ANSI_ARGS_((Message *msgPtr)); | |
152 | static int ConfigureMessage _ANSI_ARGS_((Tcl_Interp *interp, | |
153 | Message *msgPtr, int argc, char **argv, | |
154 | int flags)); | |
155 | static void DestroyMessage _ANSI_ARGS_((ClientData clientData)); | |
156 | static void DisplayMessage _ANSI_ARGS_((ClientData clientData)); | |
157 | \f | |
158 | /* | |
159 | *-------------------------------------------------------------- | |
160 | * | |
161 | * Tk_MessageCmd -- | |
162 | * | |
163 | * This procedure is invoked to process the "message" Tcl | |
164 | * command. See the user documentation for details on what | |
165 | * it does. | |
166 | * | |
167 | * Results: | |
168 | * A standard Tcl result. | |
169 | * | |
170 | * Side effects: | |
171 | * See the user documentation. | |
172 | * | |
173 | *-------------------------------------------------------------- | |
174 | */ | |
175 | ||
176 | int | |
177 | Tk_MessageCmd(clientData, interp, argc, argv) | |
178 | ClientData clientData; /* Main window associated with | |
179 | * interpreter. */ | |
180 | Tcl_Interp *interp; /* Current interpreter. */ | |
181 | int argc; /* Number of arguments. */ | |
182 | char **argv; /* Argument strings. */ | |
183 | { | |
184 | register Message *msgPtr; | |
185 | Tk_Window new; | |
186 | Tk_Window tkwin = (Tk_Window) clientData; | |
187 | ||
188 | if (argc < 2) { | |
189 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
190 | argv[0], " pathName ?options?\"", (char *) NULL); | |
191 | return TCL_ERROR; | |
192 | } | |
193 | ||
194 | new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL); | |
195 | if (new == NULL) { | |
196 | return TCL_ERROR; | |
197 | } | |
198 | ||
199 | msgPtr = (Message *) ckalloc(sizeof(Message)); | |
200 | msgPtr->tkwin = new; | |
201 | msgPtr->interp = interp; | |
202 | msgPtr->string = NULL; | |
203 | msgPtr->textVarName = NULL; | |
204 | msgPtr->border = NULL; | |
205 | msgPtr->borderWidth = 0; | |
206 | msgPtr->relief = TK_RELIEF_FLAT; | |
207 | msgPtr->fontPtr = NULL; | |
208 | msgPtr->fgColorPtr = NULL; | |
209 | msgPtr->textGC = NULL; | |
210 | msgPtr->padX = 0; | |
211 | msgPtr->padY = 0; | |
212 | msgPtr->width = 0; | |
213 | msgPtr->aspect = 150; | |
214 | msgPtr->justify = TK_JUSTIFY_LEFT; | |
215 | msgPtr->cursor = None; | |
216 | msgPtr->flags = 0; | |
217 | ||
218 | Tk_SetClass(msgPtr->tkwin, "Message"); | |
219 | Tk_CreateEventHandler(msgPtr->tkwin, ExposureMask|StructureNotifyMask, | |
220 | MessageEventProc, (ClientData) msgPtr); | |
221 | Tcl_CreateCommand(interp, Tk_PathName(msgPtr->tkwin), MessageWidgetCmd, | |
222 | (ClientData) msgPtr, (void (*)()) NULL); | |
223 | if (ConfigureMessage(interp, msgPtr, argc-2, argv+2, 0) != TCL_OK) { | |
224 | goto error; | |
225 | } | |
226 | ||
227 | interp->result = Tk_PathName(msgPtr->tkwin); | |
228 | return TCL_OK; | |
229 | ||
230 | error: | |
231 | Tk_DestroyWindow(msgPtr->tkwin); | |
232 | return TCL_ERROR; | |
233 | } | |
234 | \f | |
235 | /* | |
236 | *-------------------------------------------------------------- | |
237 | * | |
238 | * MessageWidgetCmd -- | |
239 | * | |
240 | * This procedure is invoked to process the Tcl command | |
241 | * that corresponds to a widget managed by this module. | |
242 | * See the user documentation for details on what it does. | |
243 | * | |
244 | * Results: | |
245 | * A standard Tcl result. | |
246 | * | |
247 | * Side effects: | |
248 | * See the user documentation. | |
249 | * | |
250 | *-------------------------------------------------------------- | |
251 | */ | |
252 | ||
253 | static int | |
254 | MessageWidgetCmd(clientData, interp, argc, argv) | |
255 | ClientData clientData; /* Information about message widget. */ | |
256 | Tcl_Interp *interp; /* Current interpreter. */ | |
257 | int argc; /* Number of arguments. */ | |
258 | char **argv; /* Argument strings. */ | |
259 | { | |
260 | register Message *msgPtr = (Message *) clientData; | |
261 | int length; | |
262 | char c; | |
263 | ||
264 | if (argc < 2) { | |
265 | Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], | |
266 | " option ?arg arg ...?\"", (char *) NULL); | |
267 | return TCL_ERROR; | |
268 | } | |
269 | c = argv[1][0]; | |
270 | length = strlen(argv[1]); | |
271 | if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) { | |
272 | if (argc == 2) { | |
273 | return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs, | |
274 | (char *) msgPtr, (char *) NULL, 0); | |
275 | } else if (argc == 3) { | |
276 | return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs, | |
277 | (char *) msgPtr, argv[2], 0); | |
278 | } else { | |
279 | return ConfigureMessage(interp, msgPtr, argc-2, argv+2, | |
280 | TK_CONFIG_ARGV_ONLY); | |
281 | } | |
282 | } else { | |
283 | Tcl_AppendResult(interp, "bad option \"", argv[1], | |
284 | "\": must be configure", (char *) NULL); | |
285 | return TCL_ERROR; | |
286 | } | |
287 | } | |
288 | \f | |
289 | /* | |
290 | *---------------------------------------------------------------------- | |
291 | * | |
292 | * DestroyMessage -- | |
293 | * | |
294 | * This procedure is invoked by Tk_EventuallyFree or Tk_Release | |
295 | * to clean up the internal structure of a message at a safe time | |
296 | * (when no-one is using it anymore). | |
297 | * | |
298 | * Results: | |
299 | * None. | |
300 | * | |
301 | * Side effects: | |
302 | * Everything associated with the message is freed up. | |
303 | * | |
304 | *---------------------------------------------------------------------- | |
305 | */ | |
306 | ||
307 | static void | |
308 | DestroyMessage(clientData) | |
309 | ClientData clientData; /* Info about message widget. */ | |
310 | { | |
311 | register Message *msgPtr = (Message *) clientData; | |
312 | ||
313 | if (msgPtr->string != NULL) { | |
314 | ckfree(msgPtr->string); | |
315 | } | |
316 | if (msgPtr->textVarName != NULL) { | |
317 | Tcl_UntraceVar(msgPtr->interp, msgPtr->textVarName, | |
318 | TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, | |
319 | MessageTextVarProc, (ClientData) msgPtr); | |
320 | ckfree(msgPtr->textVarName); | |
321 | } | |
322 | if (msgPtr->border != NULL) { | |
323 | Tk_Free3DBorder(msgPtr->border); | |
324 | } | |
325 | if (msgPtr->fontPtr != NULL) { | |
326 | Tk_FreeFontStruct(msgPtr->fontPtr); | |
327 | } | |
328 | if (msgPtr->fgColorPtr != NULL) { | |
329 | Tk_FreeColor(msgPtr->fgColorPtr); | |
330 | } | |
331 | if (msgPtr->textGC != None) { | |
332 | Tk_FreeGC(msgPtr->textGC); | |
333 | } | |
334 | if (msgPtr->cursor != None) { | |
335 | Tk_FreeCursor(msgPtr->cursor); | |
336 | } | |
337 | ckfree((char *) msgPtr); | |
338 | } | |
339 | \f | |
340 | /* | |
341 | *---------------------------------------------------------------------- | |
342 | * | |
343 | * ConfigureMessage -- | |
344 | * | |
345 | * This procedure is called to process an argv/argc list, plus | |
346 | * the Tk option database, in order to configure (or | |
347 | * reconfigure) a message widget. | |
348 | * | |
349 | * Results: | |
350 | * The return value is a standard Tcl result. If TCL_ERROR is | |
351 | * returned, then interp->result contains an error message. | |
352 | * | |
353 | * Side effects: | |
354 | * Configuration information, such as text string, colors, font, | |
355 | * etc. get set for msgPtr; old resources get freed, if there | |
356 | * were any. | |
357 | * | |
358 | *---------------------------------------------------------------------- | |
359 | */ | |
360 | ||
361 | static int | |
362 | ConfigureMessage(interp, msgPtr, argc, argv, flags) | |
363 | Tcl_Interp *interp; /* Used for error reporting. */ | |
364 | register Message *msgPtr; /* Information about widget; may or may | |
365 | * not already have values for some fields. */ | |
366 | int argc; /* Number of valid entries in argv. */ | |
367 | char **argv; /* Arguments. */ | |
368 | int flags; /* Flags to pass to Tk_ConfigureWidget. */ | |
369 | { | |
370 | XGCValues gcValues; | |
371 | GC newGC; | |
372 | ||
373 | /* | |
374 | * Eliminate any existing trace on a variable monitored by the message. | |
375 | */ | |
376 | ||
377 | if (msgPtr->textVarName != NULL) { | |
378 | Tcl_UntraceVar(interp, msgPtr->textVarName, | |
379 | TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, | |
380 | MessageTextVarProc, (ClientData) msgPtr); | |
381 | } | |
382 | ||
383 | if (Tk_ConfigureWidget(interp, msgPtr->tkwin, configSpecs, | |
384 | argc, argv, (char *) msgPtr, flags) != TCL_OK) { | |
385 | return TCL_ERROR; | |
386 | } | |
387 | ||
388 | /* | |
389 | * If the message is to display the value of a variable, then set up | |
390 | * a trace on the variable's value, create the variable if it doesn't | |
391 | * exist, and fetch its current value. | |
392 | */ | |
393 | ||
394 | if (msgPtr->textVarName != NULL) { | |
395 | char *value; | |
396 | ||
397 | value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY); | |
398 | if (value == NULL) { | |
399 | Tcl_SetVar(interp, msgPtr->textVarName, msgPtr->string, | |
400 | TCL_GLOBAL_ONLY); | |
401 | } else { | |
402 | if (msgPtr->string != NULL) { | |
403 | ckfree(msgPtr->string); | |
404 | } | |
405 | msgPtr->string = ckalloc((unsigned) (strlen(value) + 1)); | |
406 | strcpy(msgPtr->string, value); | |
407 | } | |
408 | Tcl_TraceVar(interp, msgPtr->textVarName, | |
409 | TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, | |
410 | MessageTextVarProc, (ClientData) msgPtr); | |
411 | } | |
412 | ||
413 | /* | |
414 | * A few other options need special processing, such as setting | |
415 | * the background from a 3-D border or handling special defaults | |
416 | * that couldn't be specified to Tk_ConfigureWidget. | |
417 | */ | |
418 | ||
419 | msgPtr->numChars = strlen(msgPtr->string); | |
420 | ||
421 | Tk_SetBackgroundFromBorder(msgPtr->tkwin, msgPtr->border); | |
422 | ||
423 | gcValues.font = msgPtr->fontPtr->fid; | |
424 | gcValues.foreground = msgPtr->fgColorPtr->pixel; | |
425 | newGC = Tk_GetGC(msgPtr->tkwin, GCForeground|GCFont, | |
426 | &gcValues); | |
427 | if (msgPtr->textGC != None) { | |
428 | Tk_FreeGC(msgPtr->textGC); | |
429 | } | |
430 | msgPtr->textGC = newGC; | |
431 | ||
432 | if (msgPtr->padX == -1) { | |
433 | msgPtr->padX = msgPtr->fontPtr->ascent/2; | |
434 | } | |
435 | ||
436 | if (msgPtr->padY == -1) { | |
437 | msgPtr->padY = msgPtr->fontPtr->ascent/4; | |
438 | } | |
439 | ||
440 | if (msgPtr->justify == TK_JUSTIFY_FILL) { | |
441 | interp->result = "can't use \"fill\" justify style in messages"; | |
442 | return TCL_ERROR; | |
443 | } | |
444 | ||
445 | /* | |
446 | * Recompute the desired geometry for the window, and arrange for | |
447 | * the window to be redisplayed. | |
448 | */ | |
449 | ||
450 | ComputeMessageGeometry(msgPtr); | |
451 | if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin) | |
452 | && !(msgPtr->flags & REDRAW_PENDING)) { | |
453 | Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr); | |
454 | msgPtr->flags |= REDRAW_PENDING|CLEAR_NEEDED; | |
455 | } | |
456 | ||
457 | return TCL_OK; | |
458 | } | |
459 | \f | |
460 | /* | |
461 | *-------------------------------------------------------------- | |
462 | * | |
463 | * ComputeMessageGeometry -- | |
464 | * | |
465 | * Compute the desired geometry for a message window, | |
466 | * taking into account the desired aspect ratio for the | |
467 | * window. | |
468 | * | |
469 | * Results: | |
470 | * None. | |
471 | * | |
472 | * Side effects: | |
473 | * Tk_GeometryRequest is called to inform the geometry | |
474 | * manager of the desired geometry for this window. | |
475 | * | |
476 | *-------------------------------------------------------------- | |
477 | */ | |
478 | ||
479 | static void | |
480 | ComputeMessageGeometry(msgPtr) | |
481 | register Message *msgPtr; /* Information about window. */ | |
482 | { | |
483 | char *p; | |
484 | int width, inc, height, numLines; | |
485 | int thisWidth, maxWidth; | |
486 | int aspect, lowerBound, upperBound; | |
487 | ||
488 | /* | |
489 | * Compute acceptable bounds for the final aspect ratio. | |
490 | */ | |
491 | aspect = msgPtr->aspect/10; | |
492 | if (aspect < 5) { | |
493 | aspect = 5; | |
494 | } | |
495 | lowerBound = msgPtr->aspect - aspect; | |
496 | upperBound = msgPtr->aspect + aspect; | |
497 | ||
498 | /* | |
499 | * Do the computation in multiple passes: start off with | |
500 | * a very wide window, and compute its height. Then change | |
501 | * the width and try again. Reduce the size of the change | |
502 | * and iterate until dimensions are found that approximate | |
503 | * the desired aspect ratio. Or, if the user gave an explicit | |
504 | * width then just use that. | |
505 | */ | |
506 | ||
507 | if (msgPtr->width > 0) { | |
508 | width = msgPtr->width; | |
509 | inc = 0; | |
510 | } else { | |
511 | width = WidthOfScreen(Tk_Screen(msgPtr->tkwin))/2; | |
512 | inc = width/2; | |
513 | } | |
514 | for ( ; ; inc /= 2) { | |
515 | maxWidth = 0; | |
516 | for (numLines = 1, p = msgPtr->string; ; numLines++) { | |
517 | if (*p == '\n') { | |
518 | p++; | |
519 | continue; | |
520 | } | |
521 | p += TkMeasureChars(msgPtr->fontPtr, p, | |
522 | msgPtr->numChars - (p - msgPtr->string), 0, width, | |
523 | TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &thisWidth); | |
524 | if (thisWidth > maxWidth) { | |
525 | maxWidth = thisWidth; | |
526 | } | |
527 | if (*p == 0) { | |
528 | break; | |
529 | } | |
530 | ||
531 | /* | |
532 | * Skip spaces and tabs at the beginning of a line, unless | |
533 | * they follow a user-requested newline. | |
534 | */ | |
535 | ||
536 | while (isspace(*p)) { | |
537 | if (*p == '\n') { | |
538 | p++; | |
539 | break; | |
540 | } | |
541 | p++; | |
542 | } | |
543 | } | |
544 | ||
545 | height = numLines * (msgPtr->fontPtr->ascent | |
546 | + msgPtr->fontPtr->descent) + 2*msgPtr->borderWidth | |
547 | + 2*msgPtr->padY; | |
548 | if (inc <= 2) { | |
549 | break; | |
550 | } | |
551 | aspect = (100*(maxWidth + 2*msgPtr->borderWidth | |
552 | + 2*msgPtr->padX))/height; | |
553 | if (aspect < lowerBound) { | |
554 | width += inc; | |
555 | } else if (aspect > upperBound) { | |
556 | width -= inc; | |
557 | } else { | |
558 | break; | |
559 | } | |
560 | } | |
561 | msgPtr->lineLength = maxWidth; | |
562 | msgPtr->msgHeight = numLines * (msgPtr->fontPtr->ascent | |
563 | + msgPtr->fontPtr->descent); | |
564 | Tk_GeometryRequest(msgPtr->tkwin, | |
565 | maxWidth + 2*msgPtr->borderWidth + 2*msgPtr->padX, height); | |
566 | Tk_SetInternalBorder(msgPtr->tkwin, msgPtr->borderWidth); | |
567 | } | |
568 | \f | |
569 | /* | |
570 | *-------------------------------------------------------------- | |
571 | * | |
572 | * DisplayMessage -- | |
573 | * | |
574 | * This procedure redraws the contents of a message window. | |
575 | * | |
576 | * Results: | |
577 | * None. | |
578 | * | |
579 | * Side effects: | |
580 | * Information appears on the screen. | |
581 | * | |
582 | *-------------------------------------------------------------- | |
583 | */ | |
584 | ||
585 | static void | |
586 | DisplayMessage(clientData) | |
587 | ClientData clientData; /* Information about window. */ | |
588 | { | |
589 | register Message *msgPtr = (Message *) clientData; | |
590 | register Tk_Window tkwin = msgPtr->tkwin; | |
591 | char *p; | |
592 | int x, y, lineLength, numChars, charsLeft; | |
593 | ||
594 | msgPtr->flags &= ~REDRAW_PENDING; | |
595 | if ((msgPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { | |
596 | return; | |
597 | } | |
598 | if (msgPtr->flags & CLEAR_NEEDED) { | |
599 | XClearWindow(Tk_Display(tkwin), Tk_WindowId(tkwin)); | |
600 | msgPtr->flags &= ~CLEAR_NEEDED; | |
601 | } | |
602 | ||
603 | /* | |
604 | * Compute starting y-location for message based on message size | |
605 | * and anchor option. | |
606 | */ | |
607 | ||
608 | switch (msgPtr->anchor) { | |
609 | case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE: | |
610 | y = msgPtr->borderWidth + msgPtr->padY; | |
611 | break; | |
612 | case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E: | |
613 | y = (Tk_Height(tkwin) - msgPtr->msgHeight)/2; | |
614 | break; | |
615 | default: | |
616 | y = Tk_Height(tkwin) - msgPtr->borderWidth - msgPtr->padY | |
617 | - msgPtr->msgHeight; | |
618 | break; | |
619 | } | |
620 | y += msgPtr->fontPtr->ascent; | |
621 | ||
622 | /* | |
623 | * Work through the string to display one line at a time. | |
624 | * Display each line in three steps. First compute the | |
625 | * line's width, then figure out where to display the | |
626 | * line to justify it properly, then display the line. | |
627 | */ | |
628 | ||
629 | for (p = msgPtr->string, charsLeft = msgPtr->numChars; *p != 0; | |
630 | y += msgPtr->fontPtr->ascent + msgPtr->fontPtr->descent) { | |
631 | if (*p == '\n') { | |
632 | p++; | |
633 | charsLeft--; | |
634 | continue; | |
635 | } | |
636 | numChars = TkMeasureChars(msgPtr->fontPtr, p, charsLeft, 0, | |
637 | msgPtr->lineLength, TK_WHOLE_WORDS|TK_AT_LEAST_ONE, | |
638 | &lineLength); | |
639 | switch (msgPtr->anchor) { | |
640 | case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW: | |
641 | x = msgPtr->borderWidth + msgPtr->padX; | |
642 | break; | |
643 | case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S: | |
644 | x = (Tk_Width(tkwin) - msgPtr->lineLength)/2; | |
645 | break; | |
646 | default: | |
647 | x = Tk_Width(tkwin) - msgPtr->borderWidth - msgPtr->padX | |
648 | - msgPtr->lineLength; | |
649 | break; | |
650 | } | |
651 | if (msgPtr->justify == TK_JUSTIFY_CENTER) { | |
652 | x += (msgPtr->lineLength - lineLength)/2; | |
653 | } else if (msgPtr->justify == TK_JUSTIFY_RIGHT) { | |
654 | x += msgPtr->lineLength - lineLength; | |
655 | } | |
656 | TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin), | |
657 | msgPtr->textGC, msgPtr->fontPtr, p, numChars, x, y, 0); | |
658 | p += numChars; | |
659 | charsLeft -= numChars; | |
660 | ||
661 | /* | |
662 | * Skip blanks at the beginning of a line, unless they follow | |
663 | * a user-requested newline. | |
664 | */ | |
665 | ||
666 | while (isspace(*p)) { | |
667 | charsLeft--; | |
668 | if (*p == '\n') { | |
669 | p++; | |
670 | break; | |
671 | } | |
672 | p++; | |
673 | } | |
674 | } | |
675 | ||
676 | if (msgPtr->relief != TK_RELIEF_FLAT) { | |
677 | Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin), | |
678 | msgPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), | |
679 | msgPtr->borderWidth, msgPtr->relief); | |
680 | } | |
681 | } | |
682 | \f | |
683 | /* | |
684 | *-------------------------------------------------------------- | |
685 | * | |
686 | * MessageEventProc -- | |
687 | * | |
688 | * This procedure is invoked by the Tk dispatcher for various | |
689 | * events on messages. | |
690 | * | |
691 | * Results: | |
692 | * None. | |
693 | * | |
694 | * Side effects: | |
695 | * When the window gets deleted, internal structures get | |
696 | * cleaned up. When it gets exposed, it is redisplayed. | |
697 | * | |
698 | *-------------------------------------------------------------- | |
699 | */ | |
700 | ||
701 | static void | |
702 | MessageEventProc(clientData, eventPtr) | |
703 | ClientData clientData; /* Information about window. */ | |
704 | XEvent *eventPtr; /* Information about event. */ | |
705 | { | |
706 | Message *msgPtr = (Message *) clientData; | |
707 | ||
708 | if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) { | |
709 | if ((msgPtr->tkwin != NULL) && !(msgPtr->flags & REDRAW_PENDING)) { | |
710 | Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr); | |
711 | msgPtr->flags |= REDRAW_PENDING; | |
712 | } | |
713 | } else if (eventPtr->type == DestroyNotify) { | |
714 | Tcl_DeleteCommand(msgPtr->interp, Tk_PathName(msgPtr->tkwin)); | |
715 | msgPtr->tkwin = NULL; | |
716 | if (msgPtr->flags & REDRAW_PENDING) { | |
717 | Tk_CancelIdleCall(DisplayMessage, (ClientData) msgPtr); | |
718 | } | |
719 | Tk_EventuallyFree((ClientData) msgPtr, DestroyMessage); | |
720 | } | |
721 | } | |
722 | \f | |
723 | /* | |
724 | *-------------------------------------------------------------- | |
725 | * | |
726 | * MessageTextVarProc -- | |
727 | * | |
728 | * This procedure is invoked when someone changes the variable | |
729 | * whose contents are to be displayed in a message. | |
730 | * | |
731 | * Results: | |
732 | * NULL is always returned. | |
733 | * | |
734 | * Side effects: | |
735 | * The text displayed in the message will change to match the | |
736 | * variable. | |
737 | * | |
738 | *-------------------------------------------------------------- | |
739 | */ | |
740 | ||
741 | /* ARGSUSED */ | |
742 | static char * | |
743 | MessageTextVarProc(clientData, interp, name1, name2, flags) | |
744 | ClientData clientData; /* Information about message. */ | |
745 | Tcl_Interp *interp; /* Interpreter containing variable. */ | |
746 | char *name1; /* Name of variable. */ | |
747 | char *name2; /* Second part of variable name. */ | |
748 | int flags; /* Information about what happened. */ | |
749 | { | |
750 | register Message *msgPtr = (Message *) clientData; | |
751 | char *value; | |
752 | ||
753 | /* | |
754 | * If the variable is unset, then immediately recreate it unless | |
755 | * the whole interpreter is going away. | |
756 | */ | |
757 | ||
758 | if (flags & TCL_TRACE_UNSETS) { | |
759 | if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { | |
760 | Tcl_SetVar2(interp, name1, name2, msgPtr->string, | |
761 | flags & TCL_GLOBAL_ONLY); | |
762 | Tcl_TraceVar2(interp, name1, name2, | |
763 | TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, | |
764 | MessageTextVarProc, clientData); | |
765 | } | |
766 | return (char *) NULL; | |
767 | } | |
768 | ||
769 | value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY); | |
770 | if (value == NULL) { | |
771 | value = ""; | |
772 | } | |
773 | if (msgPtr->string != NULL) { | |
774 | ckfree(msgPtr->string); | |
775 | } | |
776 | msgPtr->numChars = strlen(value); | |
777 | msgPtr->string = ckalloc((unsigned) (msgPtr->numChars + 1)); | |
778 | strcpy(msgPtr->string, value); | |
779 | ComputeMessageGeometry(msgPtr); | |
780 | ||
781 | msgPtr->flags |= CLEAR_NEEDED; | |
782 | if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin) | |
783 | && !(msgPtr->flags & REDRAW_PENDING)) { | |
784 | Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr); | |
785 | msgPtr->flags |= REDRAW_PENDING; | |
786 | } | |
787 | return (char *) NULL; | |
788 | } |