home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tk3.3b1 / tkMessage.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-16  |  23.4 KB  |  789 lines

  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 (c) 1990-1993 The Regents of the University of California.
  9.  * All rights reserved.
  10.  *
  11.  * Permission is hereby granted, without written agreement and without
  12.  * license or royalty fees, to use, copy, modify, and distribute this
  13.  * software and its documentation for any purpose, provided that the
  14.  * above copyright notice and the following two paragraphs appear in
  15.  * all copies of this software.
  16.  * 
  17.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  18.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  19.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  20.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  21.  *
  22.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  23.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  24.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  25.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  26.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  27.  */
  28.  
  29. #ifndef lint
  30. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMessage.c,v 1.39 93/06/16 17:15:48 ouster Exp $ SPRITE (Berkeley)";
  31. #endif
  32.  
  33. #include "tkConfig.h"
  34. #include "default.h"
  35. #include "tkInt.h"
  36.  
  37. /*
  38.  * A data structure of the following type is kept for each message
  39.  * widget managed by this file:
  40.  */
  41.  
  42. typedef struct {
  43.     Tk_Window tkwin;        /* Window that embodies the message.  NULL
  44.                  * means that the window has been destroyed
  45.                  * but the data structures haven't yet been
  46.                  * cleaned up.*/
  47.     Display *display;        /* Display containing widget.  Used, among
  48.                  * other things, so that resources can be
  49.                  * freed even after tkwin has gone away. */
  50.     Tcl_Interp *interp;        /* Interpreter associated with message. */
  51.     Tk_Uid string;        /* String displayed in message. */
  52.     int numChars;        /* Number of characters in string, not
  53.                  * including terminating NULL character. */
  54.     char *textVarName;        /* Name of variable (malloc'ed) or NULL.
  55.                  * If non-NULL, message displays the contents
  56.                  * of this variable. */
  57.  
  58.     /*
  59.      * Information used when displaying widget:
  60.      */
  61.  
  62.     Tk_3DBorder border;        /* Structure used to draw 3-D border and
  63.                  * background.  NULL means a border hasn't
  64.                  * been created yet. */
  65.     int borderWidth;        /* Width of border. */
  66.     int relief;            /* 3-D effect: TK_RELIEF_RAISED, etc. */
  67.     XFontStruct *fontPtr;    /* Information about text font, or NULL. */
  68.     XColor *fgColorPtr;        /* Foreground color in normal mode. */
  69.     GC textGC;            /* GC for drawing text in normal mode. */
  70.     int padX, padY;        /* User-requested extra space around text. */
  71.     Tk_Anchor anchor;        /* Where to position text within window region
  72.                  * if window is larger or smaller than
  73.                  * needed. */
  74.     int width;            /* User-requested width, in pixels.  0 means
  75.                  * compute width using aspect ratio below. */
  76.     int aspect;            /* Desired aspect ratio for window
  77.                  * (100*width/height). */
  78.     int lineLength;        /* Length of each line, in pixels.  Computed
  79.                  * from width and/or aspect. */
  80.     int msgHeight;        /* Total number of pixels in vertical direction
  81.                  * needed to display message. */
  82.     Tk_Justify justify;        /* Justification for text. */
  83.  
  84.     /*
  85.      * Miscellaneous information:
  86.      */
  87.  
  88.     Cursor cursor;        /* Current cursor for window, or None. */
  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.  * CLEAR_NEEDED;        Need to clear the window when redrawing.
  100.  */
  101.  
  102. #define REDRAW_PENDING        1
  103. #define CLEAR_NEEDED        2
  104.  
  105. /*
  106.  * Information used for argv parsing.
  107.  */
  108.  
  109.  
  110. static Tk_ConfigSpec configSpecs[] = {
  111.     {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
  112.     DEF_MESSAGE_ANCHOR, Tk_Offset(Message, anchor), 0},
  113.     {TK_CONFIG_INT, "-aspect", "aspect", "Aspect",
  114.     DEF_MESSAGE_ASPECT, Tk_Offset(Message, aspect), 0},
  115.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  116.     DEF_MESSAGE_BG_COLOR, Tk_Offset(Message, border),
  117.     TK_CONFIG_COLOR_ONLY},
  118.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  119.     DEF_MESSAGE_BG_MONO, Tk_Offset(Message, border),
  120.     TK_CONFIG_MONO_ONLY},
  121.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  122.     (char *) NULL, 0, 0},
  123.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  124.     (char *) NULL, 0, 0},
  125.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  126.     DEF_MESSAGE_BORDER_WIDTH, Tk_Offset(Message, borderWidth), 0},
  127.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  128.     DEF_MESSAGE_CURSOR, Tk_Offset(Message, cursor), TK_CONFIG_NULL_OK},
  129.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  130.     (char *) NULL, 0, 0},
  131.     {TK_CONFIG_FONT, "-font", "font", "Font",
  132.     DEF_MESSAGE_FONT, Tk_Offset(Message, fontPtr), 0},
  133.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  134.     DEF_MESSAGE_FG, Tk_Offset(Message, fgColorPtr), 0},
  135.     {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
  136.     DEF_MESSAGE_JUSTIFY, Tk_Offset(Message, justify), 0},
  137.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  138.     DEF_MESSAGE_PADX, Tk_Offset(Message, padX), 0},
  139.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  140.     DEF_MESSAGE_PADY, Tk_Offset(Message, padY), 0},
  141.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  142.     DEF_MESSAGE_RELIEF, Tk_Offset(Message, relief), 0},
  143.     {TK_CONFIG_STRING, "-text", "text", "Text",
  144.     DEF_MESSAGE_TEXT, Tk_Offset(Message, string), 0},
  145.     {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
  146.     DEF_MESSAGE_TEXT_VARIABLE, Tk_Offset(Message, textVarName),
  147.     TK_CONFIG_NULL_OK},
  148.     {TK_CONFIG_PIXELS, "-width", "width", "Width",
  149.     DEF_MESSAGE_WIDTH, Tk_Offset(Message, width), 0},
  150.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  151.     (char *) NULL, 0, 0}
  152. };
  153.  
  154. /*
  155.  * Forward declarations for procedures defined later in this file:
  156.  */
  157.  
  158. static void        MessageEventProc _ANSI_ARGS_((ClientData clientData,
  159.                 XEvent *eventPtr));
  160. static char *        MessageTextVarProc _ANSI_ARGS_((ClientData clientData,
  161.                 Tcl_Interp *interp, char *name1, char *name2,
  162.                 int flags));
  163. static int        MessageWidgetCmd _ANSI_ARGS_((ClientData clientData,
  164.                 Tcl_Interp *interp, int argc, char **argv));
  165. static void        ComputeMessageGeometry _ANSI_ARGS_((Message *msgPtr));
  166. static int        ConfigureMessage _ANSI_ARGS_((Tcl_Interp *interp,
  167.                 Message *msgPtr, int argc, char **argv,
  168.                 int flags));
  169. static void        DestroyMessage _ANSI_ARGS_((ClientData clientData));
  170. static void        DisplayMessage _ANSI_ARGS_((ClientData clientData));
  171.  
  172. /*
  173.  *--------------------------------------------------------------
  174.  *
  175.  * Tk_MessageCmd --
  176.  *
  177.  *    This procedure is invoked to process the "message" Tcl
  178.  *    command.  See the user documentation for details on what
  179.  *    it does.
  180.  *
  181.  * Results:
  182.  *    A standard Tcl result.
  183.  *
  184.  * Side effects:
  185.  *    See the user documentation.
  186.  *
  187.  *--------------------------------------------------------------
  188.  */
  189.  
  190. int
  191. Tk_MessageCmd(clientData, interp, argc, argv)
  192.     ClientData clientData;    /* Main window associated with
  193.                  * interpreter. */
  194.     Tcl_Interp *interp;        /* Current interpreter. */
  195.     int argc;            /* Number of arguments. */
  196.     char **argv;        /* Argument strings. */
  197. {
  198.     register Message *msgPtr;
  199.     Tk_Window new;
  200.     Tk_Window tkwin = (Tk_Window) clientData;
  201.  
  202.     if (argc < 2) {
  203.     Tcl_AppendResult(interp, "wrong # args:  should be \"",
  204.         argv[0], " pathName ?options?\"", (char *) NULL);
  205.     return TCL_ERROR;
  206.     }
  207.  
  208.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  209.     if (new == NULL) {
  210.     return TCL_ERROR;
  211.     }
  212.  
  213.     msgPtr = (Message *) ckalloc(sizeof(Message));
  214.     msgPtr->tkwin = new;
  215.     msgPtr->display = Tk_Display(new);
  216.     msgPtr->interp = interp;
  217.     msgPtr->textVarName = NULL;
  218.     msgPtr->borderWidth = 0;
  219.     msgPtr->relief = TK_RELIEF_FLAT;
  220.     msgPtr->textGC = NULL;
  221.     msgPtr->padX = 0;
  222.     msgPtr->padY = 0;
  223.     msgPtr->width = 0;
  224.     msgPtr->aspect = 150;
  225.     msgPtr->flags = 0;
  226.  
  227.     Tk_SetClass(msgPtr->tkwin, "Message");
  228.     Tk_CreateEventHandler(msgPtr->tkwin, ExposureMask|StructureNotifyMask,
  229.         MessageEventProc, (ClientData) msgPtr);
  230.     Tcl_CreateCommand(interp, Tk_PathName(msgPtr->tkwin), MessageWidgetCmd,
  231.         (ClientData) msgPtr, (void (*)()) NULL);
  232.     if (ConfigureMessage(interp, msgPtr, argc-2, argv+2, 0) != TCL_OK) {
  233.     goto error;
  234.     }
  235.  
  236.     interp->result = Tk_PathName(msgPtr->tkwin);
  237.     return TCL_OK;
  238.  
  239.     error:
  240.     Tk_DestroyWindow(msgPtr->tkwin);
  241.     return TCL_ERROR;
  242. }
  243.  
  244. /*
  245.  *--------------------------------------------------------------
  246.  *
  247.  * MessageWidgetCmd --
  248.  *
  249.  *    This procedure is invoked to process the Tcl command
  250.  *    that corresponds to a widget managed by this module.
  251.  *    See the user documentation for details on what it does.
  252.  *
  253.  * Results:
  254.  *    A standard Tcl result.
  255.  *
  256.  * Side effects:
  257.  *    See the user documentation.
  258.  *
  259.  *--------------------------------------------------------------
  260.  */
  261.  
  262. static int
  263. MessageWidgetCmd(clientData, interp, argc, argv)
  264.     ClientData clientData;    /* Information about message widget. */
  265.     Tcl_Interp *interp;        /* Current interpreter. */
  266.     int argc;            /* Number of arguments. */
  267.     char **argv;        /* Argument strings. */
  268. {
  269.     register Message *msgPtr = (Message *) clientData;
  270.     int length;
  271.     char c;
  272.  
  273.     if (argc < 2) {
  274.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  275.         " option ?arg arg ...?\"", (char *) NULL);
  276.     return TCL_ERROR;
  277.     }
  278.     c = argv[1][0];
  279.     length = strlen(argv[1]);
  280.     if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
  281.     if (argc == 2) {
  282.         return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs,
  283.             (char *) msgPtr, (char *) NULL, 0);
  284.     } else if (argc == 3) {
  285.         return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs,
  286.             (char *) msgPtr, argv[2], 0);
  287.     } else {
  288.         return ConfigureMessage(interp, msgPtr, argc-2, argv+2,
  289.             TK_CONFIG_ARGV_ONLY);
  290.     }
  291.     } else {
  292.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  293.         "\":  must be configure", (char *) NULL);
  294.     return TCL_ERROR;
  295.     }
  296. }
  297.  
  298. /*
  299.  *----------------------------------------------------------------------
  300.  *
  301.  * DestroyMessage --
  302.  *
  303.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  304.  *    to clean up the internal structure of a message at a safe time
  305.  *    (when no-one is using it anymore).
  306.  *
  307.  * Results:
  308.  *    None.
  309.  *
  310.  * Side effects:
  311.  *    Everything associated with the message is freed up.
  312.  *
  313.  *----------------------------------------------------------------------
  314.  */
  315.  
  316. static void
  317. DestroyMessage(clientData)
  318.     ClientData clientData;    /* Info about message widget. */
  319. {
  320.     register Message *msgPtr = (Message *) clientData;
  321.  
  322.     /*
  323.      * Free up all the stuff that requires special handling, then
  324.      * let Tk_FreeOptions handle all the standard option-related
  325.      * stuff.
  326.      */
  327.  
  328.     if (msgPtr->textVarName != NULL) {
  329.     Tcl_UntraceVar(msgPtr->interp, msgPtr->textVarName,
  330.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  331.         MessageTextVarProc, (ClientData) msgPtr);
  332.     }
  333.     if (msgPtr->textGC != None) {
  334.     Tk_FreeGC(msgPtr->display, msgPtr->textGC);
  335.     }
  336.     Tk_FreeOptions(configSpecs, (char *) msgPtr, msgPtr->display, 0);
  337.     ckfree((char *) msgPtr);
  338. }
  339.  
  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->display, 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.  
  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.  
  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(msgPtr->display, 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(msgPtr->display, 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(msgPtr->display, Tk_WindowId(tkwin),
  678.         msgPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  679.         msgPtr->borderWidth, msgPtr->relief);
  680.     }
  681. }
  682.  
  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.  
  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. }
  789.