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