Logo Search packages:      
Sourcecode: saods9 version File versions

bltHtext.c

/*
 * bltHtext.c --
 *
 *    This module implements a hypertext widget for the BLT toolkit.
 *
 * Copyright 1991-1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 * The "htext" widget was created by George Howlett.
 */

/*
 * To do:
 *
 * 1) Fix scroll unit round off errors.
 *
 * 2) Better error checking.
 *
 * 3) Use html format.
 *
 * 4) The dimension of cavities using -relwidth and -relheight
 *    should be 0 when computing initial estimates for the size
 *    of the virtual text.
 */

#include "bltInt.h"

#ifndef NO_HTEXT
#include <bltChain.h>
#include <bltHash.h>
#include "bltTile.h"
 
#include <sys/stat.h>
#include <X11/Xatom.h>

#define DEF_LINES_ALLOC 512   /* Default block of lines allocated */
#define CLAMP(val,low,hi)     \
      (((val) < (low)) ? (low) : ((val) > (hi)) ? (hi) : (val))

/*
 * Justify option values
 */
typedef enum {
    JUSTIFY_CENTER, JUSTIFY_TOP, JUSTIFY_BOTTOM
} Justify;

extern Tk_CustomOption bltFillOption;
extern Tk_CustomOption bltPadOption;
extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltTileOption;

static int StringToWidth _ANSI_ARGS_((ClientData clientData,
      Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
      int flags));
static int StringToHeight _ANSI_ARGS_((ClientData clientData,
      Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
      int flags));
static char *WidthHeightToString _ANSI_ARGS_((ClientData clientData,
      Tk_Window tkwin, char *widgRec, int offset, Tcl_FreeProc **freeProc));

static Tk_CustomOption widthOption =
{
    StringToWidth, WidthHeightToString, (ClientData)0
};

static Tk_CustomOption heightOption =
{
    StringToHeight, WidthHeightToString, (ClientData)0
};

static int StringToJustify _ANSI_ARGS_((ClientData clientData,
      Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
      int offset));
static char *JustifyToString _ANSI_ARGS_((ClientData clientData,
      Tk_Window tkwin, char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption justifyOption =
{
    StringToJustify, JustifyToString, (ClientData)0
};


static void EmbeddedWidgetGeometryProc _ANSI_ARGS_((ClientData, Tk_Window));
static void EmbeddedWidgetCustodyProc _ANSI_ARGS_((ClientData, Tk_Window));

static Tk_GeomMgr htextMgrInfo =
{
    "htext",                  /* Name of geometry manager used by winfo */
    EmbeddedWidgetGeometryProc,     /* Procedure to for new geometry requests */
    EmbeddedWidgetCustodyProc,      /* Procedure when window is taken away */
};


/*
 * Line --
 *
 *    Structure to contain the contents of a single line of text and
 *    the widgets on that line.
 *
 *    Individual lines are not configurable, although changes to the
 *    size of widgets do effect its values.
 */
typedef struct {
    int offset;               /* Offset of line from y-origin (0) in
                         * world coordinates */
    int baseline;       /* Baseline y-coordinate of the text */
    short int width, height;  /* Dimensions of the line */
    int textStart, textEnd;   /* Start and end indices of characters
                         * forming the line in the text array */
    Blt_Chain *chainPtr;      /* Chain of embedded widgets on the line of 
                         * text */
} Line;

typedef struct {
    int textStart;
    int textEnd;
} Segment;

typedef struct {
    int x, y;
} Position;

/*
 * Hypertext widget.
 */
typedef struct {
    Tk_Window tkwin;          /* Window that embodies the widget.
                                 * NULL means that the window has been
                                 * destroyed but the data structures
                                 * haven't yet been cleaned up.*/
    Display *display;         /* Display containing widget; needed,
                                 * among other things, to release
                                 * resources after tkwin has already
                                 * gone away. */
    Tcl_Interp *interp;       /* Interpreter associated with widget. */

    Tcl_Command cmdToken;     /* Token for htext's widget command. */
    int flags;

    /* User-configurable fields */

    XColor *normalFg, *normalBg;
    Tk_Font font;       /* Font for normal text. May affect the size
                         * of the viewport if the width/height is
                         * specified in columns/rows */
    GC drawGC;                /* Graphics context for normal text */
    Blt_Tile tile;
    int tileOffsetPage;       /* Set tile offset to top of page instead
                         * of toplevel window */
    GC fillGC;                /* GC for clearing the window in the
                         * designated background color. The
                         * background color is the foreground
                         * attribute in GC.  */

    int nRows, nColumns;      /* # of characters of the current font
                         * for a row or column of the viewport.
                         * Used to determine the width and height
                         * of the text window (i.e. viewport) */
    int reqWidth, reqHeight;  /* Requested dimensions of the viewport */
    int maxWidth, maxHeight;  /* Maximum dimensions allowed for the viewport,
                         * regardless of the size of the text */

    Tk_Cursor cursor;         /* X Cursor */

    char *fileName;           /* If non-NULL, indicates the name of a
                         * hypertext file to be read into the widget.
                         * If NULL, the *text* field is considered
                         * instead */
    char *text;               /* Hypertext to be loaded into the widget. This
                         * value is ignored if *fileName* is non-NULL */
    int specChar;       /* Special character designating a TCL
                           * command block in a hypertext file. */
    int leader;               /* # of pixels between lines */

    char *yScrollCmdPrefix;   /* Name of vertical scrollbar to invoke */
    int yScrollUnits;         /* # of pixels per vertical scroll */
    char *xScrollCmdPrefix;   /* Name of horizontal scroll bar to invoke */
    int xScrollUnits;         /* # of pixels per horizontal scroll */

    int reqLineNum;           /* Line requested by "goto" command */

    /*
     * The view port is the width and height of the window and the
     * origin of the viewport (upper left corner) in world coordinates.
     */
    int worldWidth, worldHeight;/* Size of view text in world coordinates */
    int xOffset, yOffset;     /* Position of viewport in world coordinates */

    int pendingX, pendingY;   /* New upper-left corner (origin) of
                         * the viewport (not yet posted) */

    int first, last;          /* Range of lines displayed */

    int lastWidth, lastHeight;
    /* Last known size of the window: saved to
                         * recognize when the viewport is resized. */

    Blt_HashTable widgetTable;      /* Table of embedded widgets. */

    /*
     * Selection display information:
     */
    Tk_3DBorder selBorder;    /* Border and background color */
    int selBorderWidth;       /* Border width */
    XColor *selFgColor;       /* Text foreground color */
    GC selectGC;        /* GC for drawing selected text */
    int selAnchor;            /* Fixed end of selection
                           * (i.e. "selection to" operation will
                           * use this as one end of the selection).*/
    int selFirst;       /* The index of first character in the
                         * text array selected */
    int selLast;        /* The index of the last character selected */
    int exportSelection;      /* Non-zero means to export the internal text
                         * selection to the X server. */
    char *takeFocus;

    /*
     * Scanning information:
     */
    XPoint scanMark;          /* Anchor position of scan */
    XPoint scanPt;            /* x,y position where the scan started. */

    char *charArr;            /* Pool of characters representing the text
                         * to be displayed */
    int nChars;               /* Length of the text pool */

    Line *lineArr;            /* Array of pointers to text lines */
    int nLines;               /* # of line entered into array. */
    int arraySize;            /* Size of array allocated. */

} HText;

/*
 * Bit flags for the hypertext widget:
 */
#define REDRAW_PENDING   (1<<0)     /* A DoWhenIdle handler has already
                         * been queued to redraw the window */
#define IGNORE_EXPOSURES (1<<1)     /* Ignore exposure events in the text
                         * window.  Potentially many expose
                         * events can occur while rearranging
                         * embedded widgets during a single call to
                         * the DisplayText.  */

#define REQUEST_LAYOUT  (1<<4)      /* Something has happened which
                         * requires the layout of text and
                         * embedded widget positions to be
                         * recalculated.  The following
                         * actions may cause this:
                         *
                         * 1) the contents of the hypertext
                         *    has changed by either the -file or
                         *    -text options.
                         *
                         * 2) a text attribute has changed
                         *    (line spacing, font, etc)
                         *
                         * 3) a embedded widget has been resized or
                         *    moved.
                         *
                         * 4) a widget configuration option has
                         *    changed.
                         */
#define TEXT_DIRTY      (1<<5)      /* The layout was recalculated and the
                         * size of the world (text layout) has
                         * changed. */
#define GOTO_PENDING    (1<<6)      /* Indicates the starting text line
                         * number has changed. To be reflected
                         * the next time the widget is redrawn. */
#define WIDGET_APPENDED (1<<7)      /* Indicates a embedded widget has just
                         * been appended to the text.  This is
                         * used to determine when to add a
                         * space to the text array */

#define DEF_HTEXT_BACKGROUND        STD_NORMAL_BACKGROUND
#define DEF_HTEXT_BG_MONO           STD_NORMAL_BG_MONO
#define DEF_HTEXT_CURSOR            "arrow"
#define DEF_HTEXT_EXPORT_SELECTION  "1"

#define DEF_HTEXT_FOREGROUND        STD_NORMAL_FOREGROUND
#define DEF_HTEXT_FG_MONO           STD_NORMAL_FG_MONO
#define DEF_HTEXT_FILE_NAME         (char *)NULL
#define DEF_HTEXT_FONT              STD_FONT
#define DEF_HTEXT_HEIGHT            "0"
#define DEF_HTEXT_LINE_SPACING            "1"
#define DEF_HTEXT_MAX_HEIGHT        (char *)NULL
#define DEF_HTEXT_MAX_WIDTH         (char *)NULL
#define DEF_HTEXT_SCROLL_UNITS            "10"
#define DEF_HTEXT_SPEC_CHAR         "0x25"
#define DEF_HTEXT_SELECT_BORDERWIDTH      STD_SELECT_BORDERWIDTH
#define DEF_HTEXT_SELECT_BACKGROUND       STD_SELECT_BACKGROUND
#define DEF_HTEXT_SELECT_BG_MONO    STD_SELECT_BG_MONO
#define DEF_HTEXT_SELECT_FOREGROUND       STD_SELECT_FOREGROUND
#define DEF_HTEXT_SELECT_FG_MONO    STD_SELECT_FG_MONO
#define DEF_HTEXT_TAKE_FOCUS        "1"
#define DEF_HTEXT_TEXT              (char *)NULL
#define DEF_HTEXT_TILE_OFFSET       "1"
#define DEF_HTEXT_WIDTH             "0"

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_COLOR, "-background", "background", "Background",
      DEF_HTEXT_BACKGROUND, Tk_Offset(HText, normalBg), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-background", "background", "Background",
      DEF_HTEXT_BG_MONO, Tk_Offset(HText, normalBg), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
      DEF_HTEXT_CURSOR, Tk_Offset(HText, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection", "ExportSelection",
      DEF_HTEXT_EXPORT_SELECTION, Tk_Offset(HText, exportSelection), 0},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
    {TK_CONFIG_STRING, "-file", "file", "File",
      DEF_HTEXT_FILE_NAME, Tk_Offset(HText, fileName), TK_CONFIG_NULL_OK},
    {TK_CONFIG_FONT, "-font", "font", "Font",
      DEF_HTEXT_FONT, Tk_Offset(HText, font), 0},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
      DEF_HTEXT_FOREGROUND, Tk_Offset(HText, normalFg), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
      DEF_HTEXT_FG_MONO, Tk_Offset(HText, normalFg), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-height", "height", "Height",
      DEF_HTEXT_HEIGHT, Tk_Offset(HText, reqHeight),
      TK_CONFIG_DONT_SET_DEFAULT, &heightOption},
    {TK_CONFIG_CUSTOM, "-linespacing", "lineSpacing", "LineSpacing",
      DEF_HTEXT_LINE_SPACING, Tk_Offset(HText, leader),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-maxheight", "maxHeight", "MaxHeight",
      DEF_HTEXT_MAX_HEIGHT, Tk_Offset(HText, maxHeight),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-maxwidth", "maxWidth", "MaxWidth",
      DEF_HTEXT_MAX_WIDTH, Tk_Offset(HText, maxWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
      DEF_HTEXT_SELECT_BG_MONO, Tk_Offset(HText, selBorder),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
      DEF_HTEXT_SELECT_BACKGROUND, Tk_Offset(HText, selBorder),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_CUSTOM, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
      DEF_HTEXT_SELECT_BORDERWIDTH, Tk_Offset(HText, selBorderWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
      DEF_HTEXT_SELECT_FG_MONO, Tk_Offset(HText, selFgColor),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
      DEF_HTEXT_SELECT_FOREGROUND, Tk_Offset(HText, selFgColor),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_INT, "-specialchar", "specialChar", "SpecialChar",
      DEF_HTEXT_SPEC_CHAR, Tk_Offset(HText, specChar), 0},
    {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
      DEF_HTEXT_TAKE_FOCUS, Tk_Offset(HText, takeFocus),
      TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile",
      (char *)NULL, Tk_Offset(HText, tile), TK_CONFIG_NULL_OK,
      &bltTileOption},
    {TK_CONFIG_BOOLEAN, "-tileoffset", "tileOffset", "TileOffset",
      DEF_HTEXT_TILE_OFFSET, Tk_Offset(HText, tileOffsetPage), 0},
    {TK_CONFIG_STRING, "-text", "text", "Text",
      DEF_HTEXT_TEXT, Tk_Offset(HText, text), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
      DEF_HTEXT_WIDTH, Tk_Offset(HText, reqWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &widthOption},
    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
      (char *)NULL, Tk_Offset(HText, xScrollCmdPrefix), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-xscrollunits", "xScrollUnits", "ScrollUnits",
      DEF_HTEXT_SCROLL_UNITS, Tk_Offset(HText, xScrollUnits),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
      (char *)NULL, Tk_Offset(HText, yScrollCmdPrefix), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-yscrollunits", "yScrollUnits", "yScrollUnits",
      DEF_HTEXT_SCROLL_UNITS, Tk_Offset(HText, yScrollUnits),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
      (char *)NULL, 0, 0}
};

typedef struct {
    HText *htPtr;       /* Pointer to parent's Htext structure */
    Tk_Window tkwin;          /* Widget window */
    int flags;

    int x, y;                 /* Origin of embedded widget in text */

    int cavityWidth, cavityHeight; /* Dimensions of the cavity
                            * surrounding the embedded widget */
    /*
     *  Dimensions of the embedded widget.  Compared against actual
     *      embedded widget sizes when checking for resizing.
     */
    int winWidth, winHeight;

    int precedingTextEnd;     /* Index (in charArr) of the the last
                         * character immediatedly preceding
                         * the embedded widget */
    int precedingTextWidth;   /* Width of normal text preceding widget. */

    Tk_Anchor anchor;
    Justify justify;          /* Justification of region wrt to line */

    /*
     * Requested dimensions of the cavity (includes padding). If non-zero,
     * it overrides the calculated dimension of the cavity.
     */
    int reqCavityWidth, reqCavityHeight;

    /*
     * Relative dimensions of cavity wrt the size of the viewport. If
     * greater than 0.0.
     */
    double relCavityWidth, relCavityHeight;

    int reqWidth, reqHeight;  /* If non-zero, overrides the requested
                         * dimension of the embedded widget */

    double relWidth, relHeight;     /* Relative dimensions of embedded
                         * widget wrt the size of the viewport */

    Blt_Pad padX, padY;       /* Extra padding to frame around */

    int ipadX, ipadY;         /* internal padding for window */

    int fill;                 /* Fill style flag */

} EmbeddedWidget;

/*
 * Flag bits embedded widgets:
 */
#define WIDGET_VISIBLE  (1<<2)      /* Widget is currently visible in the
                         * viewport. */
#define WIDGET_NOT_CHILD (1<<3) /* Widget is not a child of hypertext. */
/*
 * Defaults for embedded widgets:
 */
#define DEF_WIDGET_ANCHOR        "center"
#define DEF_WIDGET_FILL       "none"
#define DEF_WIDGET_HEIGHT     "0"
#define DEF_WIDGET_JUSTIFY    "center"
#define DEF_WIDGET_PAD_X            "0"
#define DEF_WIDGET_PAD_Y            "0"
#define DEF_WIDGET_REL_HEIGHT "0.0"
#define DEF_WIDGET_REL_WIDTH        "0.0"
#define DEF_WIDGET_WIDTH      "0"

static Tk_ConfigSpec widgetConfigSpecs[] =
{
    {TK_CONFIG_ANCHOR, "-anchor", (char *)NULL, (char *)NULL,
      DEF_WIDGET_ANCHOR, Tk_Offset(EmbeddedWidget, anchor),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-fill", (char *)NULL, (char *)NULL,
      DEF_WIDGET_FILL, Tk_Offset(EmbeddedWidget, fill),
      TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-cavityheight", (char *)NULL, (char *)NULL,
      DEF_WIDGET_HEIGHT, Tk_Offset(EmbeddedWidget, reqCavityHeight),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-cavitywidth", (char *)NULL, (char *)NULL,
      DEF_WIDGET_WIDTH, Tk_Offset(EmbeddedWidget, reqCavityWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-height", (char *)NULL, (char *)NULL,
      DEF_WIDGET_HEIGHT, Tk_Offset(EmbeddedWidget, reqHeight),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-justify", (char *)NULL, (char *)NULL,
      DEF_WIDGET_JUSTIFY, Tk_Offset(EmbeddedWidget, justify),
      TK_CONFIG_DONT_SET_DEFAULT, &justifyOption},
    {TK_CONFIG_CUSTOM, "-padx", (char *)NULL, (char *)NULL,
      DEF_WIDGET_PAD_X, Tk_Offset(EmbeddedWidget, padX),
      TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_CUSTOM, "-pady", (char *)NULL, (char *)NULL,
      DEF_WIDGET_PAD_Y, Tk_Offset(EmbeddedWidget, padY),
      TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
    {TK_CONFIG_DOUBLE, "-relcavityheight", (char *)NULL, (char *)NULL,
      DEF_WIDGET_REL_HEIGHT, Tk_Offset(EmbeddedWidget, relCavityHeight),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_DOUBLE, "-relcavitywidth", (char *)NULL, (char *)NULL,
      DEF_WIDGET_REL_WIDTH, Tk_Offset(EmbeddedWidget, relCavityWidth),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_DOUBLE, "-relheight", (char *)NULL, (char *)NULL,
      DEF_WIDGET_REL_HEIGHT, Tk_Offset(EmbeddedWidget, relHeight),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_DOUBLE, "-relwidth", (char *)NULL, (char *)NULL,
      DEF_WIDGET_REL_WIDTH, Tk_Offset(EmbeddedWidget, relWidth),
      TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-width", (char *)NULL, (char *)NULL,
      DEF_WIDGET_WIDTH, Tk_Offset(EmbeddedWidget, reqWidth),
      TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
      (char *)NULL, 0, 0}
};


/* Forward Declarations */
static void DestroyText _ANSI_ARGS_((DestroyData dataPtr));
static void EmbeddedWidgetEventProc _ANSI_ARGS_((ClientData clientdata, 
      XEvent *eventPtr));
static void DisplayText _ANSI_ARGS_((ClientData clientData));
static void TextDeleteCmdProc _ANSI_ARGS_((ClientData clientdata));

static Tcl_VarTraceProc TextVarProc;
static Blt_TileChangedProc TileChangedProc;
static Tk_LostSelProc TextLostSelection;
static Tk_SelectionProc TextSelectionProc;
static Tk_EventProc TextEventProc;
static Tcl_CmdProc TextWidgetCmd;
static Tcl_CmdProc TextCmd;
/* end of Forward Declarations */


 /* Custom options */
/*
 *----------------------------------------------------------------------
 *
 * StringToJustify --
 *
 *    Converts the justification string into its numeric
 *    representation. This configuration option affects how the
 *    embedded widget is positioned with respect to the line on which
 *    it sits.
 *
 *    Valid style strings are:
 *
 *    "top"      Uppermost point of region is top of the line's
 *             text
 *    "center"   Center point of region is line's baseline.
 *    "bottom"   Lowermost point of region is bottom of the
 *             line's text
 *
 * Returns:
 *    A standard Tcl result.  If the value was not valid
 *
 *---------------------------------------------------------------------- */
/*ARGSUSED*/
static int
StringToJustify(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Not used. */
    char *string;       /* Justification string */
    char *widgRec;            /* Structure record */
    int offset;               /* Offset of justify in record */
{
    Justify *justPtr = (Justify *)(widgRec + offset);
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if ((c == 'c') && (strncmp(string, "center", length) == 0)) {
      *justPtr = JUSTIFY_CENTER;
    } else if ((c == 't') && (strncmp(string, "top", length) == 0)) {
      *justPtr = JUSTIFY_TOP;
    } else if ((c == 'b') && (strncmp(string, "bottom", length) == 0)) {
      *justPtr = JUSTIFY_BOTTOM;
    } else {
      Tcl_AppendResult(interp, "bad justification argument \"", string,
          "\": should be \"center\", \"top\", or \"bottom\"", (char *)NULL);
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NameOfJustify --
 *
 *    Returns the justification style string based upon the value.
 *
 * Results:
 *    The static justification style string is returned.
 *
 *----------------------------------------------------------------------
 */
static char *
NameOfJustify(justify)
    Justify justify;
{
    switch (justify) {
    case JUSTIFY_CENTER:
      return "center";
    case JUSTIFY_TOP:
      return "top";
    case JUSTIFY_BOTTOM:
      return "bottom";
    default:
      return "unknown justification value";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * JustifyToString --
 *
 *    Returns the justification style string based upon the value.
 *
 * Results:
 *    The justification style string is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
JustifyToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Structure record */
    int offset;               /* Offset of justify record */
    Tcl_FreeProc **freeProcPtr;     /* Not used. */
{
    Justify justify = *(Justify *)(widgRec + offset);

    return NameOfJustify(justify);
}

/*
 *----------------------------------------------------------------------
 *
 * GetScreenDistance --
 *
 *    Converts the given string into the screen distance or number
 *    of characters.  The valid formats are
 *
 *        N - pixels    Nm - millimeters
 *        Ni  - inches        Np - pica
 *          Nc  - centimeters   N# - number of characters
 *
 *    where N is a non-negative decimal number.
 *
 * Results:
 *    A standard Tcl result.  The screen distance and the number of
 *    characters are returned.  If the string can't be converted,
 *    TCL_ERROR is returned and interp->result will contain an error
 *    message.
 *
 *----------------------------------------------------------------------
 */
static int
GetScreenDistance(interp, tkwin, string, sizePtr, countPtr)
    Tcl_Interp *interp;
    Tk_Window tkwin;
    char *string;
    int *sizePtr;
    int *countPtr;
{
    int nPixels, nChars;
    char *endPtr;       /* Pointer to last character scanned */
    double value;
    int rounded;

    value = strtod(string, &endPtr);
    if (endPtr == string) {
      Tcl_AppendResult(interp, "bad screen distance \"", string, "\"",
          (char *)NULL);
      return TCL_ERROR;
    }
    if (value < 0.0) {
      Tcl_AppendResult(interp, "screen distance \"", string,
          "\" must be non-negative value", (char *)NULL);
      return TCL_ERROR;
    }
    while (isspace(UCHAR(*endPtr))) {
      if (*endPtr == '\0') {
          break;
      }
      endPtr++;
    }
    nPixels = nChars = 0;
    rounded = ROUND(value);
    switch (*endPtr) {
    case '\0':                /* Distance in pixels */
      nPixels = rounded;
      break;
    case '#':                 /* Number of characters */
      nChars = rounded;
      break;
    default:                  /* cm, mm, pica, inches */
      if (Tk_GetPixels(interp, tkwin, string, &rounded) != TCL_OK) {
          return TCL_ERROR;
      }
      nPixels = rounded;
      break;
    }
    *sizePtr = nPixels;
    *countPtr = nChars;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToHeight --
 *
 *    Like TK_CONFIG_PIXELS, but adds an extra check for negative
 *    values.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToHeight(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Window */
    char *string;       /* Pixel value string */
    char *widgRec;            /* Widget record */
    int offset;               /* Not used. */
{
    HText *htPtr = (HText *)widgRec;
    int height, nRows;

    if (GetScreenDistance(interp, tkwin, string, &height, &nRows) != TCL_OK) {
      return TCL_ERROR;
    }
    htPtr->nRows = nRows;
    htPtr->reqHeight = height;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToWidth --
 *
 *    Like TK_CONFIG_PIXELS, but adds an extra check for negative
 *    values.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToWidth(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Window */
    char *string;       /* Pixel value string */
    char *widgRec;            /* Widget record */
    int offset;               /* Not used. */
{
    HText *htPtr = (HText *)widgRec;
    int width, nColumns;

    if (GetScreenDistance(interp, tkwin, string, &width,
          &nColumns) != TCL_OK) {
      return TCL_ERROR;
    }
    htPtr->nColumns = nColumns;
    htPtr->reqWidth = width;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * WidthHeightToString --
 *
 *    Returns the string representing the positive pixel size.
 *
 * Results:
 *    The pixel size string is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
WidthHeightToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Row/column structure record */
    int offset;               /* Offset of fill in Partition record */
    Tcl_FreeProc **freeProcPtr;     /* Not used. */
{
    int pixels = *(int *)(widgRec + offset);
    char *result;
    char string[200];

    sprintf(string, "%d", pixels);
    result = Blt_Strdup(string);
    if (result == NULL) {
      return "out of memory";
    }
    *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
    return result;
}

/* General routines */
/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedraw --
 *
 *    Queues a request to redraw the text window at the next idle
 *    point.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Information gets redisplayed.  Right now we don't do selective
 *    redisplays:  the whole window will be redrawn.  This doesn't
 *    seem to hurt performance noticeably, but if it does then this
 *    could be changed.
 *
 *----------------------------------------------------------------------
 */
static void
EventuallyRedraw(htPtr)
    HText *htPtr;       /* Information about widget. */
{
    if ((htPtr->tkwin != NULL) && !(htPtr->flags & REDRAW_PENDING)) {
      htPtr->flags |= REDRAW_PENDING;
      Tcl_DoWhenIdle(DisplayText, htPtr);
    }
}

/*
 * --------------------------------------------------------------------
 *
 * ResizeArray --
 *
 *    Reallocates memory to the new size given.  New memory
 *    is also cleared (zeros).
 *
 * Results:
 *    Returns a pointer to the new object or NULL if an error occurred.
 *
 * Side Effects:
 *    Memory is re/allocated.
 *
 * --------------------------------------------------------------------
 */
static int
ResizeArray(arrayPtr, elemSize, newSize, prevSize)
    char **arrayPtr;
    int elemSize;
    int newSize;
    int prevSize;
{
    char *newPtr;

    if (newSize == prevSize) {
      return TCL_OK;
    }
    if (newSize == 0) {       /* Free entire array */
      Blt_Free(*arrayPtr);
      *arrayPtr = NULL;
      return TCL_OK;
    }
    newPtr = Blt_Calloc(elemSize, newSize);
    if (newPtr == NULL) {
      return TCL_ERROR;
    }
    if ((prevSize > 0) && (*arrayPtr != NULL)) {
      int size;

      size = MIN(prevSize, newSize) * elemSize;
      if (size > 0) {
          memcpy(newPtr, *arrayPtr, size);
      }
      Blt_Free(*arrayPtr);
    }
    *arrayPtr = newPtr;
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * LineSearch --
 *
 *    Performs a binary search for the line of text located at some
 *    world y-coordinate (not screen y-coordinate). The search is
 *    inclusive of those lines from low to high.
 *
 * Results:
 *    Returns the array index of the line found at the given
 *    y-coordinate.  If the y-coordinate is outside of the given range
 *    of lines, -1 is returned.
 *
 * ----------------------------------------------------------------------
 */
static int
LineSearch(htPtr, yCoord, low, high)
    HText *htPtr;       /* HText widget */
    int yCoord;               /* Search y-coordinate  */
    int low, high;            /* Range of lines to search */
{
    int median;
    Line *linePtr;

    while (low <= high) {
      median = (low + high) >> 1;
      linePtr = htPtr->lineArr + median;
      if (yCoord < linePtr->offset) {
          high = median - 1;
      } else if (yCoord >= (linePtr->offset + linePtr->height)) {
          low = median + 1;
      } else {
          return median;
      }
    }
    return -1;
}

/*
 * ----------------------------------------------------------------------
 *
 * IndexSearch --
 *
 *    Try to find what line contains a given text index. Performs
 *    a binary search for the text line which contains the given index.
 *    The search is inclusive of those lines from low and high.
 *
 * Results:
 *    Returns the line number containing the given index. If the index
 *    is outside the range of lines, -1 is returned.
 *
 * ----------------------------------------------------------------------
 */
static int
IndexSearch(htPtr, key, low, high)
    HText *htPtr;       /* HText widget */
    int key;                  /* Search index */
    int low, high;            /* Range of lines to search */
{
    int median;
    Line *linePtr;

    while (low <= high) {
      median = (low + high) >> 1;
      linePtr = htPtr->lineArr + median;
      if (key < linePtr->textStart) {
          high = median - 1;
      } else if (key > linePtr->textEnd) {
          low = median + 1;
      } else {
          return median;
      }
    }
    return -1;
}

/*
 * ----------------------------------------------------------------------
 *
 * GetXYPosIndex --
 *
 *    Converts a string in the form "@x,y", where x and y are
 *    window coordinates, to a text index.
 *
 *    Window coordinates are first translated into world coordinates.
 *    Any coordinate outside of the bounds of the virtual text is
 *    silently set the nearest boundary.
 *
 * Results:
 *    A standard Tcl result.  If "string" is a valid index, then
 *    *indexPtr is filled with the numeric index corresponding.
 *    Otherwise an error message is left in interp->result.
 *
 * ----------------------------------------------------------------------
 */
static int
GetXYPosIndex(htPtr, string, indexPtr)
    HText *htPtr;
    char *string;
    int *indexPtr;
{
    int x, y, curX, dummy;
    int textLength, textStart;
    int cindex, lindex;
    Line *linePtr;

    if (Blt_GetXY(htPtr->interp, htPtr->tkwin, string, &x, &y) != TCL_OK) {
      return TCL_ERROR;
    }
    /* Locate the line corresponding to the window y-coordinate position */

    y += htPtr->yOffset;
    if (y < 0) {
      lindex = htPtr->first;
    } else if (y >= htPtr->worldHeight) {
      lindex = htPtr->last;
    } else {
      lindex = LineSearch(htPtr, y, 0, htPtr->nLines - 1);
    }
    if (lindex < 0) {
      Tcl_AppendResult(htPtr->interp, "can't find line at \"", string, "\"",
          (char *)NULL);
      return TCL_ERROR;
    }
    x += htPtr->xOffset;
    if (x < 0) {
      x = 0;
    } else if (x > htPtr->worldWidth) {
      x = htPtr->worldWidth;
    }
    linePtr = htPtr->lineArr + lindex;
    curX = 0;
    textStart = linePtr->textStart;
    textLength = linePtr->textEnd - linePtr->textStart;
    if (Blt_ChainGetLength(linePtr->chainPtr) > 0) {
      Blt_ChainLink *linkPtr;
      int deltaX;
      EmbeddedWidget *winPtr;

      for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          winPtr = Blt_ChainGetValue(linkPtr);
          deltaX = winPtr->precedingTextWidth + winPtr->cavityWidth;
          if ((curX + deltaX) > x) {
            textLength = (winPtr->precedingTextEnd - textStart);
            break;
          }
          curX += deltaX;
          /*
           * Skip over the trailing space. It designates the position of
           * a embedded widget in the text
           */
          textStart = winPtr->precedingTextEnd + 1;
      }
    }
    cindex = Tk_MeasureChars(htPtr->font, htPtr->charArr + textStart,
      textLength, 10000, DEF_TEXT_FLAGS, &dummy);
    *indexPtr = textStart + cindex;
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * ParseIndex --
 *
 *    Parse a string representing a text index into numeric
 *    value.  A text index can be in one of the following forms.
 *
 *      "anchor"  - anchor position of the selection.
 *      "sel.first"   - index of the first character in the selection.
 *      "sel.last"      - index of the last character in the selection.
 *      "page.top"      - index of the first character on the page.
 *      "page.bottom"   - index of the last character on the page.
 *      "@x,y"    - x and y are window coordinates.
 *      "number   - raw index of text
 *      "line.char"     - line number and character position
 *
 * Results:
 *    A standard Tcl result.  If "string" is a valid index, then
 *    *indexPtr is filled with the corresponding numeric index.
 *    Otherwise an error message is left in interp->result.
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */
static int
ParseIndex(htPtr, string, indexPtr)
    HText *htPtr;       /* Text for which the index is being
                         * specified. */
    char *string;       /* Numerical index into htPtr's element
                         * list, or "end" to refer to last element. */
    int *indexPtr;            /* Where to store converted relief. */
{
    unsigned int length;
    char c;
    Tcl_Interp *interp = htPtr->interp;

    length = strlen(string);
    c = string[0];

    if ((c == 'a') && (strncmp(string, "anchor", length) == 0)) {
      *indexPtr = htPtr->selAnchor;
    } else if ((c == 's') && (length > 4)) {
      if (strncmp(string, "sel.first", length) == 0) {
          *indexPtr = htPtr->selFirst;
      } else if (strncmp(string, "sel.last", length) == 0) {
          *indexPtr = htPtr->selLast;
      } else {
          goto badIndex;      /* Not a valid index */
      }
      if (*indexPtr < 0) {
          Tcl_AppendResult(interp, "bad index \"", string,
            "\": nothing selected in \"",
            Tk_PathName(htPtr->tkwin), "\"", (char *)NULL);
          return TCL_ERROR;
      }
    } else if ((c == 'p') && (length > 5) &&
      (strncmp(string, "page.top", length) == 0)) {
      int first;

      first = htPtr->first;
      if (first < 0) {
          first = 0;
      }
      *indexPtr = htPtr->lineArr[first].textStart;
    } else if ((c == 'p') && (length > 5) &&
      (strncmp(string, "page.bottom", length) == 0)) {
      *indexPtr = htPtr->lineArr[htPtr->last].textEnd;
    } else if (c == '@') {    /* Screen position */
      if (GetXYPosIndex(htPtr, string, indexPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    } else {
      char *period;

      period = strchr(string, '.');
      if (period == NULL) {   /* Raw index */
          int tindex;

          if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
            tindex = htPtr->nChars - 1;
          } else if (Tcl_GetInt(interp, string, &tindex) != TCL_OK) {
            goto badIndex;
          }
          if (tindex < 0) {
            tindex = 0;
          } else if (tindex > (htPtr->nChars - 1)) {
            tindex = htPtr->nChars - 1;
          }
          *indexPtr = tindex;
      } else {
          int lindex, cindex, offset;
          Line *linePtr;
          int result;

          *period = '\0';
          result = TCL_OK;
          if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
            lindex = htPtr->nLines - 1;
          } else {
            result = Tcl_GetInt(interp, string, &lindex);
          }
          *period = '.';      /* Repair index string before returning */
          if (result != TCL_OK) {
            goto badIndex;    /* Bad line number */
          }
          if (lindex < 0) {
            lindex = 0; /* Silently repair bad line numbers */
          }
          if (htPtr->nChars == 0) {
            *indexPtr = 0;
            return TCL_OK;
          }
          if (lindex >= htPtr->nLines) {
            lindex = htPtr->nLines - 1;
          }
          linePtr = htPtr->lineArr + lindex;
          cindex = 0;
          if ((*(period + 1) != '\0')) {
            string = period + 1;
            if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
                cindex = linePtr->textEnd - linePtr->textStart;
            } else if (Tcl_GetInt(interp, string, &cindex) != TCL_OK) {
                goto badIndex;
            }
          }
          if (cindex < 0) {
            cindex = 0; /* Silently fix bogus indices */
          }
          offset = 0;
          if (htPtr->nChars > 0) {
            offset = linePtr->textStart + cindex;
            if (offset > linePtr->textEnd) {
                offset = linePtr->textEnd;
            }
          }
          *indexPtr = offset;
      }
    }
    if (htPtr->nChars == 0) {
      *indexPtr = 0;
    }
    return TCL_OK;

  badIndex:

    /*
     * Some of the paths here leave messages in interp->result, so we
     * have to clear it out before storing our own message.
     */
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, "bad index \"", string, "\": \
should be one of the following: anchor, sel.first, sel.last, page.bottom, \
page.top, @x,y, index, line.char", (char *)NULL);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * GetIndex --
 *
 *    Get the index from a string representing a text index.
 *
 *
 * Results:
 *    A standard Tcl result.  If "string" is a valid index, then
 *    *indexPtr is filled with the numeric index corresponding.
 *    Otherwise an error message is left in interp->result.
 *
 * Side effects:
 *    None.
 *
 *--------------------------------------------------------------
 */
static int
GetIndex(htPtr, string, indexPtr)
    HText *htPtr;       /* Text for which the index is being
                         * specified. */
    char *string;       /* Numerical index into htPtr's element
                         * list, or "end" to refer to last element. */
    int *indexPtr;            /* Where to store converted relief. */
{
    int tindex;

    if (ParseIndex(htPtr, string, &tindex) != TCL_OK) {
      return TCL_ERROR;
    }
    *indexPtr = tindex;
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * GetTextPosition --
 *
 *    Performs a binary search for the index located on line in
 *    the text. The search is limited to those lines between
 *    low and high inclusive.
 *
 * Results:
 *    Returns the line number at the given Y coordinate. If position
 *    does not correspond to any of the lines in the given the set,
 *    -1 is returned.
 *
 * ----------------------------------------------------------------------
 */
static int
GetTextPosition(htPtr, tindex, lindexPtr, cindexPtr)
    HText *htPtr;
    int tindex;
    int *lindexPtr;
    int *cindexPtr;
{
    int lindex, cindex;

    lindex = cindex = 0;
    if (htPtr->nChars > 0) {
      Line *linePtr;

      lindex = IndexSearch(htPtr, tindex, 0, htPtr->nLines - 1);
      if (lindex < 0) {
          char string[200];

          sprintf(string, "can't determine line number from index \"%d\"",
            tindex);
          Tcl_AppendResult(htPtr->interp, string, (char *)NULL);
          return TCL_ERROR;
      }
      linePtr = htPtr->lineArr + lindex;
      if (tindex > linePtr->textEnd) {
          tindex = linePtr->textEnd;
      }
      cindex = tindex - linePtr->textStart;
    }
    *lindexPtr = lindex;
    *cindexPtr = cindex;
    return TCL_OK;
}

/* EmbeddedWidget Procedures */
/*
 *----------------------------------------------------------------------
 *
 * GetEmbeddedWidgetWidth --
 *
 *    Returns the width requested by the embedded widget. The requested
 *    space also includes any internal padding which has been designated
 *    for this window.
 *
 * Results:
 *    Returns the requested width of the embedded widget.
 *
 *----------------------------------------------------------------------
 */
static int
GetEmbeddedWidgetWidth(winPtr)
    EmbeddedWidget *winPtr;
{
    int width;

    if (winPtr->reqWidth > 0) {
      width = winPtr->reqWidth;
    } else if (winPtr->relWidth > 0.0) {
      width = (int)
          ((double)Tk_Width(winPtr->htPtr->tkwin) * winPtr->relWidth + 0.5);
    } else {
      width = Tk_ReqWidth(winPtr->tkwin);
    }
    width += (2 * winPtr->ipadX);
    return width;
}

/*
 *----------------------------------------------------------------------
 *
 * GetEmbeddedWidgetHeight --
 *
 *    Returns the height requested by the embedded widget. The requested
 *    space also includes any internal padding which has been designated
 *    for this window.
 *
 * Results:
 *    Returns the requested height of the embedded widget.
 *
 *----------------------------------------------------------------------
 */
static int
GetEmbeddedWidgetHeight(winPtr)
    EmbeddedWidget *winPtr;
{
    int height;

    if (winPtr->reqHeight > 0) {
      height = winPtr->reqHeight;
    } else if (winPtr->relHeight > 0.0) {
      height = (int)((double)Tk_Height(winPtr->htPtr->tkwin) *
          winPtr->relHeight + 0.5);
    } else {
      height = Tk_ReqHeight(winPtr->tkwin);
    }
    height += (2 * winPtr->ipadY);
    return height;
}

/*
 * --------------------------------------------------------------
 *
 * EmbeddedWidgetEventProc --
 *
 *    This procedure is invoked by the Tk dispatcher for various
 *    events on hypertext widgets.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    When the window gets deleted, internal structures get
 *    cleaned up.  When it gets exposed, it is redisplayed.
 *
 * --------------------------------------------------------------
 */
static void
EmbeddedWidgetEventProc(clientData, eventPtr)
    ClientData clientData;    /* Information about the embedded widget. */
    XEvent *eventPtr;         /* Information about event. */
{
    EmbeddedWidget *winPtr = clientData;
    HText *htPtr;

    if ((winPtr == NULL) || (winPtr->tkwin == NULL)) {
      return;
    }
    htPtr = winPtr->htPtr;

    if (eventPtr->type == DestroyNotify) {
      Blt_HashEntry *hPtr;
      /*
       * Mark the widget as deleted by dereferencing the Tk window
       * pointer.  Zero out the height and width to collapse the area
       * used by the widget.  Redraw the window only if the widget is
       * currently visible.
       */
      winPtr->htPtr->flags |= REQUEST_LAYOUT;
      if (Tk_IsMapped(winPtr->tkwin) && (winPtr->flags & WIDGET_VISIBLE)) {
          EventuallyRedraw(htPtr);
      }
      Tk_DeleteEventHandler(winPtr->tkwin, StructureNotifyMask,
          EmbeddedWidgetEventProc, winPtr);
      hPtr = Blt_FindHashEntry(&(htPtr->widgetTable), (char *)winPtr->tkwin);
      Blt_DeleteHashEntry(&(htPtr->widgetTable), hPtr);
      winPtr->cavityWidth = winPtr->cavityHeight = 0;
      winPtr->tkwin = NULL;

    } else if (eventPtr->type == ConfigureNotify) {
      /*
       * EmbeddedWidgets can't request new positions. Worry only about resizing.
       */
      if (winPtr->winWidth != Tk_Width(winPtr->tkwin) ||
          winPtr->winHeight != Tk_Height(winPtr->tkwin)) {
          EventuallyRedraw(htPtr);
          htPtr->flags |= REQUEST_LAYOUT;
      }
    }
}

/*
 *--------------------------------------------------------------
 *
 * EmbeddedWidgetCustodyProc --
 *
 *    This procedure is invoked when a embedded widget has been
 *    stolen by another geometry manager.  The information and
 *    memory associated with the embedded widget is released.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Arranges for the widget formerly associated with the widget
 *    to have its layout re-computed and arranged at the
 *    next idle point.
 *
 *--------------------------------------------------------------
 */
 /* ARGSUSED */
static void
EmbeddedWidgetCustodyProc(clientData, tkwin)
    ClientData clientData;    /* Information about the former embedded widget. */
    Tk_Window tkwin;          /* Not used. */
{
    Blt_HashEntry *hPtr;
    EmbeddedWidget *winPtr = clientData;
    /*
     * Mark the widget as deleted by dereferencing the Tk window
     * pointer.  Zero out the height and width to collapse the area
     * used by the widget.  Redraw the window only if the widget is
     * currently visible.
     */
    winPtr->htPtr->flags |= REQUEST_LAYOUT;
    if (Tk_IsMapped(winPtr->tkwin) && (winPtr->flags & WIDGET_VISIBLE)) {
      EventuallyRedraw(winPtr->htPtr);
    }
    Tk_DeleteEventHandler(winPtr->tkwin, StructureNotifyMask,
      EmbeddedWidgetEventProc, winPtr);
    hPtr = Blt_FindHashEntry(&(winPtr->htPtr->widgetTable), 
                       (char *)winPtr->tkwin);
    Blt_DeleteHashEntry(&(winPtr->htPtr->widgetTable), hPtr);
    winPtr->cavityWidth = winPtr->cavityHeight = 0;
    winPtr->tkwin = NULL;
}

/*
 *--------------------------------------------------------------
 *
 * EmbeddedWidgetGeometryProc --
 *
 *    This procedure is invoked by Tk_GeometryRequest for
 *    embedded widgets managed by the hypertext widget.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Arranges for tkwin, and all its managed siblings, to
 *    be repacked and drawn at the next idle point.
 *
 *--------------------------------------------------------------
 */
 /* ARGSUSED */
static void
EmbeddedWidgetGeometryProc(clientData, tkwin)
    ClientData clientData;    /* Information about window that got new
                           * preferred geometry.  */
    Tk_Window tkwin;          /* Other Tk-related information about the
                           * window. */
{
    EmbeddedWidget *winPtr = clientData;

    winPtr->htPtr->flags |= REQUEST_LAYOUT;
    EventuallyRedraw(winPtr->htPtr);
}

/*
 * ----------------------------------------------------------------------
 *
 * FindEmbeddedWidget --
 *
 *    Searches for a widget matching the path name given
 *    If found, the pointer to the widget structure is returned,
 *    otherwise NULL.
 *
 * Results:
 *    The pointer to the widget structure. If not found, NULL.
 *
 * ----------------------------------------------------------------------
 */
static EmbeddedWidget *
FindEmbeddedWidget(htPtr, tkwin)
    HText *htPtr;       /* Hypertext widget structure */
    Tk_Window tkwin;          /* Path name of embedded widget  */
{
    Blt_HashEntry *hPtr;

    hPtr = Blt_FindHashEntry(&(htPtr->widgetTable), (char *)tkwin);
    if (hPtr != NULL) {
      return (EmbeddedWidget *) Blt_GetHashValue(hPtr);
    }
    return NULL;
}

/*
 * ----------------------------------------------------------------------
 *
 * CreateEmbeddedWidget --
 *
 *    This procedure creates and initializes a new embedded widget
 *    in the hyper text widget.
 *
 * Results:
 *    The return value is a pointer to a structure describing the
 *    new embedded widget.  If an error occurred, then the return 
 *    value is NULL and an error message is left in interp->result.
 *
 * Side effects:
 *    Memory is allocated. EmbeddedWidget window is mapped. 
 *    Callbacks are set up for embedded widget resizes and geometry 
 *    requests.
 *
 * ----------------------------------------------------------------------
 */
static EmbeddedWidget *
CreateEmbeddedWidget(htPtr, name)
    HText *htPtr;       /* Hypertext widget */
    char *name;               /* Name of embedded widget */
{
    EmbeddedWidget *winPtr;
    Tk_Window tkwin;
    Blt_HashEntry *hPtr;
    int isNew;

    tkwin = Tk_NameToWindow(htPtr->interp, name, htPtr->tkwin);
    if (tkwin == NULL) {
      return NULL;
    }
    if (Tk_Parent(tkwin) != htPtr->tkwin) {
      Tcl_AppendResult(htPtr->interp, "parent window of \"", name,
          "\" must be \"", Tk_PathName(htPtr->tkwin), "\"", (char *)NULL);
      return NULL;
    }
    hPtr = Blt_CreateHashEntry(&(htPtr->widgetTable), (char *)tkwin, &isNew);
    /* Check is the widget is already embedded into this widget */
    if (!isNew) {
      Tcl_AppendResult(htPtr->interp, "\"", name,
          "\" is already appended to ", Tk_PathName(htPtr->tkwin),
          (char *)NULL);
      return NULL;
    }
    winPtr = Blt_Calloc(1, sizeof(EmbeddedWidget));
    assert(winPtr);
    winPtr->flags = 0;
    winPtr->tkwin = tkwin;
    winPtr->htPtr = htPtr;
    winPtr->x = winPtr->y = 0;
    winPtr->fill = FILL_NONE;
    winPtr->justify = JUSTIFY_CENTER;
    winPtr->anchor = TK_ANCHOR_CENTER;
    Blt_SetHashValue(hPtr, winPtr);

    Tk_ManageGeometry(tkwin, &htextMgrInfo, winPtr);
    Tk_CreateEventHandler(tkwin, StructureNotifyMask, EmbeddedWidgetEventProc,
        winPtr);
    return winPtr;
}

/*
 * ----------------------------------------------------------------------
 *
 * DestroyEmbeddedWidget --
 *
 *    This procedure is invoked by DestroyLine to clean up the
 *    internal structure of a widget.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Everything associated with the widget is freed up.
 *
 * ----------------------------------------------------------------------
 */
static void
DestroyEmbeddedWidget(winPtr)
    EmbeddedWidget *winPtr;
{
    /* Destroy the embedded widget if it still exists */
    if (winPtr->tkwin != NULL) {
      Blt_HashEntry *hPtr;

      Tk_DeleteEventHandler(winPtr->tkwin, StructureNotifyMask,
          EmbeddedWidgetEventProc, winPtr);
      hPtr = Blt_FindHashEntry(&(winPtr->htPtr->widgetTable),
          (char *)winPtr->tkwin);
      Blt_DeleteHashEntry(&(winPtr->htPtr->widgetTable), hPtr);
      Tk_DestroyWindow(winPtr->tkwin);
    }
    Blt_Free(winPtr);
}

/* Line Procedures */
/*
 * ----------------------------------------------------------------------
 *
 * CreateLine --
 *
 *    This procedure creates and initializes a new line of text.
 *
 * Results:
 *    The return value is a pointer to a structure describing the new
 *    line of text.  If an error occurred, then the return value is NULL
 *    and an error message is left in interp->result.
 *
 * Side effects:
 *    Memory is allocated.
 *
 * ----------------------------------------------------------------------
 */
static Line *
CreateLine(htPtr)
    HText *htPtr;
{
    Line *linePtr;

    if (htPtr->nLines >= htPtr->arraySize) {
      if (htPtr->arraySize == 0) {
          htPtr->arraySize = DEF_LINES_ALLOC;
      } else {
          htPtr->arraySize += htPtr->arraySize;
      }
      if (ResizeArray((char **)&(htPtr->lineArr), sizeof(Line),
            htPtr->arraySize, htPtr->nLines) != TCL_OK) {
          return NULL;
      }
    }
    /* Initialize values in the new entry */

    linePtr = htPtr->lineArr + htPtr->nLines;
    linePtr->offset = 0;
    linePtr->height = linePtr->width = 0;
    linePtr->textStart = 0;
    linePtr->textEnd = -1;
    linePtr->baseline = 0;
    linePtr->chainPtr = Blt_ChainCreate();

    htPtr->nLines++;
    return linePtr;
}

/*
 * ----------------------------------------------------------------------
 *
 * DestroyLine --
 *
 *    This procedure is invoked to clean up the internal structure
 *    of a line.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Everything associated with the line (text and widgets) is
 *    freed up.
 *
 * ----------------------------------------------------------------------
 */
static void
DestroyLine(linePtr)
    Line *linePtr;
{
    Blt_ChainLink *linkPtr;
    EmbeddedWidget *winPtr;

    /* Free the list of embedded widget structures */
    for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
      linkPtr = Blt_ChainNextLink(linkPtr)) {
      winPtr = Blt_ChainGetValue(linkPtr);
      DestroyEmbeddedWidget(winPtr);
    }
    Blt_ChainDestroy(linePtr->chainPtr);
}

static void
FreeText(htPtr)
    HText *htPtr;
{
    int i;

    for (i = 0; i < htPtr->nLines; i++) {
      DestroyLine(htPtr->lineArr + i);
    }
    htPtr->nLines = 0;
    htPtr->nChars = 0;
    if (htPtr->charArr != NULL) {
      Blt_Free(htPtr->charArr);
      htPtr->charArr = NULL;
    }
}

/* Text Procedures */
/*
 * ----------------------------------------------------------------------
 *
 * DestroyText --
 *
 *    This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
 *    to clean up the internal structure of a HText at a safe time
 *    (when no-one is using it anymore).
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Everything associated with the widget is freed up.
 *
 * ----------------------------------------------------------------------
 */
static void
DestroyText(dataPtr)
    DestroyData dataPtr;      /* Info about hypertext widget. */
{
    HText *htPtr = (HText *)dataPtr;

    Tk_FreeOptions(configSpecs, (char *)htPtr, htPtr->display, 0);
    if (htPtr->drawGC != NULL) {
      Tk_FreeGC(htPtr->display, htPtr->drawGC);
    }
    if (htPtr->fillGC != NULL) {
      Tk_FreeGC(htPtr->display, htPtr->fillGC);
    }
    if (htPtr->tile != NULL) {
      Blt_FreeTile(htPtr->tile);
    }
    if (htPtr->selectGC != NULL) {
      Tk_FreeGC(htPtr->display, htPtr->selectGC);
    }
    FreeText(htPtr);
    if (htPtr->lineArr != NULL) {
      Blt_Free(htPtr->lineArr);
    }
    Blt_DeleteHashTable(&(htPtr->widgetTable));
    Blt_Free(htPtr);
}

/*
 * --------------------------------------------------------------
 *
 * TextEventProc --
 *
 *    This procedure is invoked by the Tk dispatcher for various
 *    events on hypertext widgets.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    When the window gets deleted, internal structures get
 *    cleaned up.  When it gets exposed, it is redisplayed.
 *
 * --------------------------------------------------------------
 */
static void
TextEventProc(clientData, eventPtr)
    ClientData clientData;    /* Information about window. */
    XEvent *eventPtr;         /* Information about event. */
{
    HText *htPtr = clientData;

    if (eventPtr->type == ConfigureNotify) {
      if ((htPtr->lastWidth != Tk_Width(htPtr->tkwin)) ||
          (htPtr->lastHeight != Tk_Height(htPtr->tkwin))) {
          htPtr->flags |= (REQUEST_LAYOUT | TEXT_DIRTY);
          EventuallyRedraw(htPtr);
      }
    } else if (eventPtr->type == Expose) {

      /*
       * If the Expose event was synthetic (i.e. we manufactured it
       * ourselves during a redraw operation), toggle the bit flag
       * which controls redraws.
       */

      if (eventPtr->xexpose.send_event) {
          htPtr->flags ^= IGNORE_EXPOSURES;
          return;
      }
      if ((eventPtr->xexpose.count == 0) &&
          !(htPtr->flags & IGNORE_EXPOSURES)) {
          htPtr->flags |= TEXT_DIRTY;
          EventuallyRedraw(htPtr);
      }
    } else if (eventPtr->type == DestroyNotify) {
      if (htPtr->tkwin != NULL) {
          htPtr->tkwin = NULL;
          Tcl_DeleteCommandFromToken(htPtr->interp, htPtr->cmdToken);
      }
      if (htPtr->flags & REDRAW_PENDING) {
          Tcl_CancelIdleCall(DisplayText, htPtr);
      }
      Tcl_EventuallyFree(htPtr, DestroyText);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TextDeleteCmdProc --
 *
 *    This procedure is invoked when a widget command is deleted.  If
 *    the widget isn't already in the process of being destroyed,
 *    this command destroys it.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The widget is destroyed.
 *
 *----------------------------------------------------------------------
 */

static void
TextDeleteCmdProc(clientData)
    ClientData clientData;    /* Pointer to widget record for widget. */
{
    HText *htPtr = clientData;

    /*
     * This procedure could be invoked either because the window was
     * destroyed and the command was then deleted (in which case tkwin
     * is NULL) or because the command was deleted, and then this procedure
     * destroys the widget.
     */

    if (htPtr->tkwin != NULL) {
      Tk_Window tkwin;

      tkwin = htPtr->tkwin;
      htPtr->tkwin = NULL;
      Tk_DestroyWindow(tkwin);
#ifdef ITCL_NAMESPACES
      Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL);
#endif /* ITCL_NAMESPACES */
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TileChangedProc
 *
 *    Stub for image change notifications.  Since we immediately draw
 *    the image into a pixmap, we don't care about image changes.
 *
 *    It would be better if Tk checked for NULL proc pointers.
 *
 * Results:
 *    None.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
TileChangedProc(clientData, tile)
    ClientData clientData;
    Blt_Tile tile;            /* Not used. */
{
    HText *htPtr = clientData;

    if (htPtr->tkwin != NULL) {
      EventuallyRedraw(htPtr);
    }
}

/* Configuration Procedures */
static void
ResetTextInfo(htPtr)
    HText *htPtr;
{
    htPtr->first = 0;
    htPtr->last = htPtr->nLines - 1;
    htPtr->selFirst = htPtr->selLast = -1;
    htPtr->selAnchor = 0;
    htPtr->pendingX = htPtr->pendingY = 0;
    htPtr->worldWidth = htPtr->worldHeight = 0;
    htPtr->xOffset = htPtr->yOffset = 0;
}

static Line *
GetLastLine(htPtr)
    HText *htPtr;
{
    if (htPtr->nLines == 0) {
      return CreateLine(htPtr);
    }
    return (htPtr->lineArr + (htPtr->nLines - 1));
}

/*
 * ----------------------------------------------------------------------
 *
 * ReadNamedFile --
 *
 *    Read the named file into a newly allocated buffer.
 *
 * Results:
 *    Returns the size of the allocated buffer if the file was
 *    read correctly.  Otherwise -1 is returned and "interp->result"
 *    will contain an error message.
 *
 * Side Effects:
 *    If successful, the contents of "bufferPtr" will point
 *    to the allocated buffer.
 *
 * ----------------------------------------------------------------------
 */
static int
ReadNamedFile(interp, fileName, bufferPtr)
    Tcl_Interp *interp;
    char *fileName;
    char **bufferPtr;
{
    FILE *f;
    int nRead, fileSize;
    int count, bytesLeft;
    char *buffer;
#if defined(_MSC_VER) || defined(__BORLANDC__)
#define fstat      _fstat
#define stat       _stat
#ifdef _MSC_VER
#define fileno     _fileno
#endif
#endif /* _MSC_VER || __BORLANDC__ */

    struct stat fileInfo;

    f = fopen(fileName, "r");
    if (f == NULL) {
      Tcl_AppendResult(interp, "can't open \"", fileName,
          "\" for reading: ", Tcl_PosixError(interp), (char *)NULL);
      return -1;
    }
    if (fstat(fileno(f), &fileInfo) < 0) {
      Tcl_AppendResult(interp, "can't stat \"", fileName, "\": ",
          Tcl_PosixError(interp), (char *)NULL);
      fclose(f);
      return -1;
    }
    fileSize = fileInfo.st_size + 1;
    buffer = Blt_Malloc(sizeof(char) * fileSize);
    if (buffer == NULL) {
      fclose(f);
      return -1;        /* Can't allocate memory for file buffer */
    }
    count = 0;
    for (bytesLeft = fileInfo.st_size; bytesLeft > 0; bytesLeft -= nRead) {
      nRead = fread(buffer + count, sizeof(char), bytesLeft, f);
      if (nRead < 0) {
          Tcl_AppendResult(interp, "error reading \"", fileName, "\": ",
            Tcl_PosixError(interp), (char *)NULL);
          fclose(f);
          Blt_Free(buffer);
          return -1;
      } else if (nRead == 0) {
          break;
      }
      count += nRead;
    }
    fclose(f);
    buffer[count] = '\0';
    *bufferPtr = buffer;
    return count;
}

/*
 * ----------------------------------------------------------------------
 *
 * CollectCommand --
 *
 *    Collect the characters representing a Tcl command into a
 *    given buffer.
 *
 * Results:
 *    Returns the number of bytes examined.  If an error occurred,
 *    -1 is returned and "interp->result" will contain an error
 *    message.
 *
 * Side Effects:
 *    If successful, the "cmdArr" will be filled with the string
 *    representing the Tcl command.
 *
 * ----------------------------------------------------------------------
 */

static int
CollectCommand(htPtr, inputArr, maxBytes, cmdArr)
    HText *htPtr;       /* Widget record */
    char inputArr[];          /* Array of bytes representing the htext input */
    int maxBytes;       /* Maximum number of bytes left in input */
    char cmdArr[];            /* Output buffer to be filled with the Tcl
                         * command */
{
    int c;
    int i;
    int state, count;

    /* Simply collect the all the characters until %% into a buffer */

    state = count = 0;
    for (i = 0; i < maxBytes; i++) {
      c = inputArr[i];
      if (c == htPtr->specChar) {
          state++;
      } else if ((state == 0) && (c == '\\')) {
          state = 3;
      } else {
          state = 0;
      }
      switch (state) {
      case 2:           /* End of command block found */
          cmdArr[count - 1] = '\0';
          return i;

      case 4:           /* Escaped block designator */
          cmdArr[count] = c;
          state = 0;
          break;

      default:          /* Add to command buffer */
          cmdArr[count++] = c;
          break;
      }
    }
    Tcl_AppendResult(htPtr->interp, "premature end of TCL command block",
      (char *)NULL);
    return -1;
}

/*
 * ----------------------------------------------------------------------
 *
 * ParseInput --
 *
 *    Parse the input to the HText structure into an array of lines.
 *    Each entry contains the beginning index and end index of the
 *    characters in the text array which comprise the line.
 *
 *    |*|*|*|\n|T|h|i|s| |a| |l|i|n|e| |o|f| |t|e|x|t|.|\n|*|*|*|
 *                ^                               ^
 *              textStart                         textEnd
 *
 *    Note that the end index contains the '\n'.
 *
 * Results:
 *    Returns TCL_OK or error depending if the file was read correctly.
 *
 * ----------------------------------------------------------------------
 */
static int
ParseInput(interp, htPtr, input, nBytes)
    Tcl_Interp *interp;
    HText *htPtr;
    char input[];
    int nBytes;
{
    int c;
    int i;
    char *textArr;
    char *cmdArr;
    int count, nLines;
    int length;
    int state;
    Line *linePtr;

    linePtr = CreateLine(htPtr);
    if (linePtr == NULL) {
      return TCL_ERROR; /* Error allocating the line structure */
    }
    /*  Right now, we replace the text array instead of appending to it */

    linePtr->textStart = 0;

    /* In the worst case, assume the entire input could be Tcl commands */
    cmdArr = Blt_Malloc(sizeof(char) * (nBytes + 1));

    textArr = Blt_Malloc(sizeof(char) * (nBytes + 1));
    if (htPtr->charArr != NULL) {
      Blt_Free(htPtr->charArr);
    }
    htPtr->charArr = textArr;
    htPtr->nChars = 0;

    nLines = count = state = 0;
    htPtr->flags &= ~WIDGET_APPENDED;

    for (i = 0; i < nBytes; i++) {
      c = input[i];
      if (c == htPtr->specChar) {
          state++;
      } else if (c == '\n') {
          state = -1;
      } else if ((state == 0) && (c == '\\')) {
          state = 3;
      } else {
          state = 0;
      }
      switch (state) {
      case 2:           /* Block of Tcl commands found */
          count--, i++;
          length = CollectCommand(htPtr, input + i, nBytes - i, cmdArr);
          if (length < 0) {
            goto error;
          }
          i += length;
          linePtr->textEnd = count;
          htPtr->nChars = count + 1;
          if (Tcl_Eval(interp, cmdArr) != TCL_OK) {
            goto error;
          }
          if (htPtr->flags & WIDGET_APPENDED) {
            /* Indicates the location a embedded widget in the text array */
            textArr[count++] = ' ';
            htPtr->flags &= ~WIDGET_APPENDED;
          }
          state = 0;
          break;

      case 4:           /* Escaped block designator */
          textArr[count - 1] = c;
          state = 0;
          break;

      case -1:          /* End of line or input */
          linePtr->textEnd = count;
          textArr[count++] = '\n';
          nLines++;
          linePtr = CreateLine(htPtr);
          if (linePtr == NULL) {
            goto error;
          }
          linePtr->textStart = count;
          state = 0;
          break;

      default:          /* Default action, add to text buffer */
          textArr[count++] = c;
          break;
      }
    }
    if (count > linePtr->textStart) {
      linePtr->textEnd = count;
      textArr[count++] = '\n';/* Every line must end with a '\n' */
      nLines++;
    }
    Blt_Free(cmdArr);
    /* Reset number of lines allocated */
    if (ResizeArray((char **)&(htPtr->lineArr), sizeof(Line), nLines,
          htPtr->arraySize) != TCL_OK) {
      Tcl_AppendResult(interp, "can't reallocate array of lines", (char *)NULL);
      return TCL_ERROR;
    }
    htPtr->nLines = htPtr->arraySize = nLines;
    /*  and the size of the character array */
    if (ResizeArray(&(htPtr->charArr), sizeof(char), count,
          nBytes) != TCL_OK) {
      Tcl_AppendResult(interp, "can't reallocate text character buffer",
          (char *)NULL);
      return TCL_ERROR;
    }
    htPtr->nChars = count;
    return TCL_OK;
  error:
    Blt_Free(cmdArr);
    return TCL_ERROR;
}

static int
IncludeText(interp, htPtr, fileName)
    Tcl_Interp *interp;
    HText *htPtr;
    char *fileName;
{
    char *buffer;
    int result;
    int nBytes;

    if ((htPtr->text == NULL) && (fileName == NULL)) {
      return TCL_OK;          /* Empty text string */
    }
    if (fileName != NULL) {
      nBytes = ReadNamedFile(interp, fileName, &buffer);
      if (nBytes < 0) {
          return TCL_ERROR;
      }
    } else {
      buffer = htPtr->text;
      nBytes = strlen(htPtr->text);
    }
    result = ParseInput(interp, htPtr, buffer, nBytes);
    if (fileName != NULL) {
      Blt_Free(buffer);
    }
    return result;
}

/* ARGSUSED */
static char *
TextVarProc(clientData, interp, name1, name2, flags)
    ClientData clientData;    /* Information about widget. */
    Tcl_Interp *interp;       /* Interpreter containing variable. */
    char *name1;        /* Name of variable. */
    char *name2;        /* Second part of variable name. */
    int flags;                /* Information about what happened. */
{
    HText *htPtr = clientData;
    HText *lasthtPtr;

    /* Check to see of this is the most recent trace */
    lasthtPtr = (HText *)Tcl_VarTraceInfo2(interp, name1, name2, flags,
      TextVarProc, NULL);
    if (lasthtPtr != htPtr) {
      return NULL;            /* Ignore all but most current trace */
    }
    if (flags & TCL_TRACE_READS) {
      char c;

      c = name2[0];
      if ((c == 'w') && (strcmp(name2, "widget") == 0)) {
          Tcl_SetVar2(interp, name1, name2, Tk_PathName(htPtr->tkwin),
            flags);
      } else if ((c == 'l') && (strcmp(name2, "line") == 0)) {
          char buf[80];
          int lineNum;

          lineNum = htPtr->nLines - 1;
          if (lineNum < 0) {
            lineNum = 0;
          }
          sprintf(buf, "%d", lineNum);
          Tcl_SetVar2(interp, name1, name2, buf, flags);
      } else if ((c == 'i') && (strcmp(name2, "index") == 0)) {
          char buf[80];

          sprintf(buf, "%d", htPtr->nChars - 1);
          Tcl_SetVar2(interp, name1, name2, buf, flags);
      } else if ((c == 'f') && (strcmp(name2, "file") == 0)) {
          char *fileName;

          fileName = htPtr->fileName;
          if (fileName == NULL) {
            fileName = "";
          }
          Tcl_SetVar2(interp, name1, name2, fileName, flags);
      } else {
          return "?unknown?";
      }
    }
    return NULL;
}

static char *varNames[] =
{
    "widget", "line", "file", "index", (char *)NULL
};

static void
CreateTraces(htPtr)
    HText *htPtr;
{
    char **ptr;
    static char globalCmd[] = "global htext";

    /*
     * Make the traced variables global to the widget
     */
    Tcl_Eval(htPtr->interp, globalCmd);
    for (ptr = varNames; *ptr != NULL; ptr++) {
      Tcl_TraceVar2(htPtr->interp, "htext", *ptr,
          (TCL_GLOBAL_ONLY | TCL_TRACE_READS), TextVarProc, htPtr);
    }
}

static void
DeleteTraces(htPtr)
    HText *htPtr;
{
    char **ptr;

    for (ptr = varNames; *ptr != NULL; ptr++) {
      Tcl_UntraceVar2(htPtr->interp, "htext", *ptr,
          (TCL_GLOBAL_ONLY | TCL_TRACE_READS), TextVarProc, htPtr);
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureText --
 *
 *    This procedure is called to process an argv/argc list, plus
 *    the Tk option database, in order to configure (or reconfigure)
 *    a hypertext widget.
 *
 *    The layout of the text must be calculated (by ComputeLayout)
 *    whenever particular options change; -font, -file, -linespacing
 *    and -text options. If the user has changes one of these options,
 *    it must be detected so that the layout can be recomputed. Since the
 *    coordinates of the layout are virtual, there is no need to adjust
 *    them if physical window attributes (window size, etc.)
 *    change.
 *
 * Results:
 *    The return value is a standard Tcl result.  If TCL_ERROR is
 *    returned, then interp->result contains an error message.
 *
 * Side effects:
 *    Configuration information, such as text string, colors, font,
 *    etc. get set for htPtr;  old resources get freed, if there were any.
 *    The hypertext is redisplayed.
 *
 * ----------------------------------------------------------------------
 */
static int
ConfigureText(interp, htPtr)
    Tcl_Interp *interp;       /* Used for error reporting. */
    HText *htPtr;       /* Information about widget; may or may not
                           * already have values for some fields. */
{
    XGCValues gcValues;
    unsigned long gcMask;
    GC newGC;

    if (Blt_ConfigModified(configSpecs, "-font", "-linespacing", "-file",
          "-text", "-width", "-height", (char *)NULL)) {
      /*
       * These options change the layout of the text.  Width/height
       * and rows/columns may change a relatively sized window or cavity.
       */
      htPtr->flags |= (REQUEST_LAYOUT | TEXT_DIRTY);  /* Mark for update */
    }
    gcMask = GCForeground | GCFont;
    gcValues.font = Tk_FontId(htPtr->font);
    gcValues.foreground = htPtr->normalFg->pixel;
    newGC = Tk_GetGC(htPtr->tkwin, gcMask, &gcValues);
    if (htPtr->drawGC != NULL) {
      Tk_FreeGC(htPtr->display, htPtr->drawGC);
    }
    htPtr->drawGC = newGC;

    gcValues.foreground = htPtr->selFgColor->pixel;
    newGC = Tk_GetGC(htPtr->tkwin, gcMask, &gcValues);
    if (htPtr->selectGC != NULL) {
      Tk_FreeGC(htPtr->display, htPtr->selectGC);
    }
    htPtr->selectGC = newGC;

    if (htPtr->xScrollUnits < 1) {
      htPtr->xScrollUnits = 1;
    }
    if (htPtr->yScrollUnits < 1) {
      htPtr->yScrollUnits = 1;
    }
    if (htPtr->tile != NULL) {
      Blt_SetTileChangedProc(htPtr->tile, TileChangedProc, htPtr);
    }
    gcValues.foreground = htPtr->normalBg->pixel;
    newGC = Tk_GetGC(htPtr->tkwin, gcMask, &gcValues);
    if (htPtr->fillGC != NULL) {
      Tk_FreeGC(htPtr->display, htPtr->fillGC);
    }
    htPtr->fillGC = newGC;

    if (htPtr->nColumns > 0) {
      htPtr->reqWidth =
          htPtr->nColumns * Tk_TextWidth(htPtr->font, "0", 1);
    }
    if (htPtr->nRows > 0) {
      Tk_FontMetrics fontMetrics;

      Tk_GetFontMetrics(htPtr->font, &fontMetrics);
      htPtr->reqHeight = htPtr->nRows * fontMetrics.linespace;
    }
    /*
     * If the either the -text or -file option changed, read in the
     * new text.  The -text option supersedes any -file option.
     */
    if (Blt_ConfigModified(configSpecs, "-file", "-text", (char *)NULL)) {
      int result;

      FreeText(htPtr);
      CreateTraces(htPtr);    /* Create variable traces */

      result = IncludeText(interp, htPtr, htPtr->fileName);

      DeleteTraces(htPtr);
      if (result == TCL_ERROR) {
          FreeText(htPtr);
          return TCL_ERROR;
      }
      ResetTextInfo(htPtr);
    }
    EventuallyRedraw(htPtr);
    return TCL_OK;
}

/* Layout Procedures */
/*
 * -----------------------------------------------------------------
 *
 * TranslateAnchor --
 *
 *    Translate the coordinates of a given bounding box based
 *    upon the anchor specified.  The anchor indicates where
 *    the given xy position is in relation to the bounding box.
 *
 *          nw --- n --- ne
 *          |            |     x,y ---+
 *          w   center   e      |     |
 *          |            |      +-----+
 *          sw --- s --- se
 *
 * Results:
 *    The translated coordinates of the bounding box are returned.
 *
 * -----------------------------------------------------------------
 */
static XPoint
TranslateAnchor(deltaX, deltaY, anchor)
    int deltaX, deltaY;       /* Difference between outer and inner regions
                         */
    Tk_Anchor anchor;         /* Direction of the anchor */
{
    XPoint point;

    point.x = point.y = 0;
    switch (anchor) {
    case TK_ANCHOR_NW:        /* Upper left corner */
      break;
    case TK_ANCHOR_W:         /* Left center */
      point.y = (deltaY / 2);
      break;
    case TK_ANCHOR_SW:        /* Lower left corner */
      point.y = deltaY;
      break;
    case TK_ANCHOR_N:         /* Top center */
      point.x = (deltaX / 2);
      break;
    case TK_ANCHOR_CENTER:    /* Centered */
      point.x = (deltaX / 2);
      point.y = (deltaY / 2);
      break;
    case TK_ANCHOR_S:         /* Bottom center */
      point.x = (deltaX / 2);
      point.y = deltaY;
      break;
    case TK_ANCHOR_NE:        /* Upper right corner */
      point.x = deltaX;
      break;
    case TK_ANCHOR_E:         /* Right center */
      point.x = deltaX;
      point.y = (deltaY / 2);
      break;
    case TK_ANCHOR_SE:        /* Lower right corner */
      point.x = deltaX;
      point.y = deltaY;
      break;
    }
    return point;
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeCavitySize --
 *
 *    Sets the width and height of the cavity based upon the
 *    requested size of the embedded widget.  The requested space also
 *    includes any external padding which has been designated for
 *    this window.
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    The size of the cavity is set in the embedded widget information
 *    structure.  These values can effect how the embedded widget is
 *    packed into the master window.
 *
 *----------------------------------------------------------------------
 */
static void
ComputeCavitySize(winPtr)
    EmbeddedWidget *winPtr;
{
    int width, height;
    int twiceBW;

    twiceBW = 2 * Tk_Changes(winPtr->tkwin)->border_width;
    if (winPtr->reqCavityWidth > 0) {
      width = winPtr->reqCavityWidth;
    } else if (winPtr->relCavityWidth > 0.0) {
      width = (int)((double)Tk_Width(winPtr->htPtr->tkwin) *
          winPtr->relCavityWidth + 0.5);
    } else {
      width = GetEmbeddedWidgetWidth(winPtr) + PADDING(winPtr->padX) + 
          twiceBW;
    }
    winPtr->cavityWidth = width;

    if (winPtr->reqCavityHeight > 0) {
      height = winPtr->reqCavityHeight;
    } else if (winPtr->relCavityHeight > 0.0) {
      height = (int)((double)Tk_Height(winPtr->htPtr->tkwin) *
          winPtr->relCavityHeight + 0.5);
    } else {
      height = GetEmbeddedWidgetHeight(winPtr) + PADDING(winPtr->padY) + 
          twiceBW;
    }
    winPtr->cavityHeight = height;
}

/*
 *----------------------------------------------------------------------
 *
 * LayoutLine --
 *
 *    This procedure computes the total width and height needed
 *      to contain the text and widgets for a particular line.
 *      It also calculates the baseline of the text on the line with
 *    respect to the other widgets on the line.
 *
 * Results:
 *    None.
 *
 *----------------------------------------------------------------------
 */
static void
LayoutLine(htPtr, linePtr)
    HText *htPtr;
    Line *linePtr;
{
    EmbeddedWidget *winPtr;
    int textStart, textLength;
    int maxAscent, maxDescent, maxHeight;
    int ascent, descent;
    int median;               /* Difference of font ascent/descent values */
    Blt_ChainLink *linkPtr;
    int x, y;
    int newX;
    Tk_FontMetrics fontMetrics;

    /* Initialize line defaults */
    Tk_GetFontMetrics(htPtr->font, &fontMetrics);
    maxAscent = fontMetrics.ascent;
    maxDescent = fontMetrics.descent;
    median = fontMetrics.ascent - fontMetrics.descent;
    ascent = descent = 0;     /* Suppress compiler warnings */

    /*
     * Pass 1: Determine the maximum ascent (baseline) and descent
     * needed for the line.  We'll need this for figuring the top,
     * bottom, and center anchors.
     */
    for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
      linkPtr = Blt_ChainNextLink(linkPtr)) {
      winPtr = Blt_ChainGetValue(linkPtr);
      if (winPtr->tkwin == NULL) {
          continue;
      }
      ComputeCavitySize(winPtr);

      switch (winPtr->justify) {
      case JUSTIFY_TOP:
          ascent = fontMetrics.ascent + winPtr->padTop;
          descent = winPtr->cavityHeight - fontMetrics.ascent;
          break;
      case JUSTIFY_CENTER:
          ascent = (winPtr->cavityHeight + median) / 2;
          descent = (winPtr->cavityHeight - median) / 2;
          break;
      case JUSTIFY_BOTTOM:
          ascent = winPtr->cavityHeight - fontMetrics.descent;
          descent = fontMetrics.descent;
          break;
      }
      if (descent > maxDescent) {
          maxDescent = descent;
      }
      if (ascent > maxAscent) {
          maxAscent = ascent;
      }
    }

    maxHeight = maxAscent + maxDescent + htPtr->leader;
    x = 0;              /* Always starts from x=0 */
    y = 0;              /* Suppress compiler warning */
    textStart = linePtr->textStart;

    /*
     * Pass 2: Find the placements of the text and widgets along each
     * line.
     */
    for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
      linkPtr = Blt_ChainNextLink(linkPtr)) {
      winPtr = Blt_ChainGetValue(linkPtr);
      if (winPtr->tkwin == NULL) {
          continue;
      }
      /* Get the width of the text leading to the widget. */
      textLength = (winPtr->precedingTextEnd - textStart);
      if (textLength > 0) {
          Tk_MeasureChars(htPtr->font, htPtr->charArr + textStart,
            textLength, 10000, TK_AT_LEAST_ONE, &newX);
          winPtr->precedingTextWidth = newX;
          x += newX;
      }
      switch (winPtr->justify) {
      case JUSTIFY_TOP:
          y = maxAscent - fontMetrics.ascent;
          break;
      case JUSTIFY_CENTER:
          y = maxAscent - (winPtr->cavityHeight + median) / 2;
          break;
      case JUSTIFY_BOTTOM:
          y = maxAscent + fontMetrics.descent - winPtr->cavityHeight;
          break;
      }
      winPtr->x = x, winPtr->y = y;

      /* Skip over trailing space */
      textStart = winPtr->precedingTextEnd + 1;

      x += winPtr->cavityWidth;
    }

    /*
     * This can be either the trailing piece of a line after the last widget
     * or the entire line if no widgets are embedded in it.
     */
    textLength = (linePtr->textEnd - textStart) + 1;
    if (textLength > 0) {
      Tk_MeasureChars(htPtr->font, htPtr->charArr + textStart,
          textLength, 10000, DEF_TEXT_FLAGS, &newX);
      x += newX;
    }
    /* Update line parameters */
    if ((linePtr->width != x) || (linePtr->height != maxHeight) ||
      (linePtr->baseline != maxAscent)) {
      htPtr->flags |= TEXT_DIRTY;
    }
    linePtr->width = x;
    linePtr->height = maxHeight;
    linePtr->baseline = maxAscent;
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeLayout --
 *
 *    This procedure computes the total width and height needed
 *      to contain the text and widgets from all the lines of text.
 *      It merely sums the heights and finds the maximum width of
 *    all the lines.  The width and height are needed for scrolling.
 *
 * Results:
 *    None.
 *
 *----------------------------------------------------------------------
 */
static void
ComputeLayout(htPtr)
    HText *htPtr;
{
    int count;
    Line *linePtr;
    int height, width;

    width = height = 0;
    for (count = 0; count < htPtr->nLines; count++) {
      linePtr = htPtr->lineArr + count;

      linePtr->offset = height;
      LayoutLine(htPtr, linePtr);
      height += linePtr->height;
      if (linePtr->width > width) {
          width = linePtr->width;
      }
    }
    /*
     * Set changed flag if new layout changed size of virtual text.
     */
    if ((height != htPtr->worldHeight) || (width != htPtr->worldWidth)) {
      htPtr->worldHeight = height, htPtr->worldWidth = width;
      htPtr->flags |= TEXT_DIRTY;
    }
}

/* Display Procedures */
/*
 * ----------------------------------------------------------------------
 *
 * GetVisibleLines --
 *
 *    Calculates which lines are visible using the height
 *      of the viewport and y offset from the top of the text.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Only those line between first and last inclusive are
 *    redrawn.
 *
 * ----------------------------------------------------------------------
 */
static int
GetVisibleLines(htPtr)
    HText *htPtr;
{
    int topLine, bottomLine;
    int firstY, lastY;
    int lastLine;

    if (htPtr->nLines == 0) {
      htPtr->first = 0;
      htPtr->last = -1;
      return TCL_OK;
    }
    firstY = htPtr->pendingY;
    lastLine = htPtr->nLines - 1;

    /* First line */
    topLine = LineSearch(htPtr, firstY, 0, lastLine);
    if (topLine < 0) {
      /*
       * This can't be. The y-coordinate offset must be corrupted.
       */
      fprintf(stderr, "internal error: First position not found `%d'\n",
          firstY);
      return TCL_ERROR;
    }
    htPtr->first = topLine;

    /*
     * If there is less text than window space, the bottom line is the
     * last line of text.  Otherwise search for the line at the bottom
     * of the window.
     */
    lastY = firstY + Tk_Height(htPtr->tkwin) - 1;
    if (lastY < htPtr->worldHeight) {
      bottomLine = LineSearch(htPtr, lastY, topLine, lastLine);
    } else {
      bottomLine = lastLine;
    }
    if (bottomLine < 0) {
      /*
       * This can't be. The newY offset must be corrupted.
       */
      fprintf(stderr, "internal error: Last position not found `%d'\n",
          lastY);
#ifdef notdef
      fprintf(stderr, "worldHeight=%d,height=%d,top=%d,first=%d,last=%d\n",
          htPtr->worldHeight, Tk_Height(htPtr->tkwin), firstY,
          htPtr->lineArr[topLine].offset, htPtr->lineArr[lastLine].offset);
#endif
      return TCL_ERROR;
    }
    htPtr->last = bottomLine;
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * DrawSegment --
 *
 *    Draws a line segment, designated by the segment structure.
 *    This routine handles the display of selected text by drawing
 *    a raised 3D border underneath the selected text.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The line segment is drawn on *draw*.
 *
 * ----------------------------------------------------------------------
 */
static void
DrawSegment(htPtr, draw, linePtr, x, y, segPtr)
    HText *htPtr;
    Drawable draw;
    Line *linePtr;
    int x, y;
    Segment *segPtr;
{
    int lastX, curPos, nChars;
    int textLength;
    int selStart, selEnd, selLength;
    Tk_FontMetrics fontMetrics;

#ifdef notdef
    fprintf(stderr, "DS select: first=%d,last=%d text: first=%d,last=%d\n",
      htPtr->selFirst, htPtr->selLast, segPtr->textStart, segPtr->textEnd);
#endif
    textLength = (segPtr->textEnd - segPtr->textStart) + 1;
    if (textLength < 1) {
      return;
    }
    Tk_GetFontMetrics(htPtr->font, &fontMetrics);
    if ((segPtr->textEnd < htPtr->selFirst) ||
      (segPtr->textStart > htPtr->selLast)) {   /* No selected text */
      Tk_DrawChars(htPtr->display, draw, htPtr->drawGC, htPtr->font,
          htPtr->charArr + segPtr->textStart, textLength - 1,
          x, y + linePtr->baseline);
      return;
    }
    /*
     *      Text in a segment (with selected text) may have
     *      up to three regions:
     *
     *      1) the text before the start the selection
     *      2) the selected text itself (drawn in a raised border)
     *      3) the text following the selection.
     */

    selStart = segPtr->textStart;
    selEnd = segPtr->textEnd;
    if (htPtr->selFirst > segPtr->textStart) {
      selStart = htPtr->selFirst;
    }
    if (htPtr->selLast < segPtr->textEnd) {
      selEnd = htPtr->selLast;
    }
    selLength = (selEnd - selStart) + 1;
    lastX = x;
    curPos = segPtr->textStart;

    if (selStart > segPtr->textStart) {   /* Text preceding selection */
      nChars = (selStart - segPtr->textStart);
      Tk_MeasureChars(htPtr->font, htPtr->charArr + segPtr->textStart,
          nChars, 10000, DEF_TEXT_FLAGS, &lastX);
      lastX += x;
      Tk_DrawChars(htPtr->display, draw, htPtr->drawGC, htPtr->font,
          htPtr->charArr + segPtr->textStart, nChars, x,
          y + linePtr->baseline);
      curPos = selStart;
    }
    if (selLength > 0) {      /* The selection itself */
      int width, nextX;

      Tk_MeasureChars(htPtr->font, htPtr->charArr + selStart,
          selLength, 10000, DEF_TEXT_FLAGS, &nextX);
      nextX += x;
      width = (selEnd == linePtr->textEnd)
          ? htPtr->worldWidth - htPtr->xOffset - lastX :
          nextX - lastX;
      Blt_Fill3DRectangle(htPtr->tkwin, draw, htPtr->selBorder,
          lastX, y + linePtr->baseline - fontMetrics.ascent,
          width, fontMetrics.linespace, htPtr->selBorderWidth,
          TK_RELIEF_RAISED);
      Tk_DrawChars(htPtr->display, draw, htPtr->selectGC,
          htPtr->font, htPtr->charArr + selStart, selLength,
          lastX, y + linePtr->baseline);
      lastX = nextX;
      curPos = selStart + selLength;
    }
    nChars = segPtr->textEnd - curPos;
    if (nChars > 0) {         /* Text following the selection */
      Tk_DrawChars(htPtr->display, draw, htPtr->drawGC, htPtr->font,
          htPtr->charArr + curPos, nChars - 1, lastX, y + linePtr->baseline);
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * MoveEmbeddedWidget --
 *
 *    Move a embedded widget to a new location in the hypertext
 *    parent window.  If the window has no geometry (i.e. width,
 *    or height is 0), simply unmap to window.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Each embedded widget is moved to its new location, generating
 *      Expose events in the parent for each embedded widget moved.
 *
 * ----------------------------------------------------------------------
 */
static void
MoveEmbeddedWidget(winPtr, offset)
    EmbeddedWidget *winPtr;
    int offset;
{
    int winWidth, winHeight;
    int width, height;
    int deltaX, deltaY;
    int x, y;
    int intBW;

    winWidth = GetEmbeddedWidgetWidth(winPtr);
    winHeight = GetEmbeddedWidgetHeight(winPtr);
    if ((winWidth < 1) || (winHeight < 1)) {
      if (Tk_IsMapped(winPtr->tkwin)) {
          Tk_UnmapWindow(winPtr->tkwin);
      }
      return;
    }
    intBW = Tk_Changes(winPtr->tkwin)->border_width;
    x = (winPtr->x + intBW + winPtr->padLeft) -
      winPtr->htPtr->xOffset;
    y = offset + (winPtr->y + intBW + winPtr->padTop) -
      winPtr->htPtr->yOffset;

    width = winPtr->cavityWidth - (2 * intBW + PADDING(winPtr->padX));
    if (width < 0) {
      width = 0;
    }
    if ((width < winWidth) || (winPtr->fill & FILL_X)) {
      winWidth = width;
    }
    deltaX = width - winWidth;

    height = winPtr->cavityHeight - (2 * intBW + PADDING(winPtr->padY));
    if (height < 0) {
      height = 0;
    }
    if ((height < winHeight) || (winPtr->fill & FILL_Y)) {
      winHeight = height;
    }
    deltaY = height - winHeight;

    if ((deltaX > 0) || (deltaY > 0)) {
      XPoint point;

      point = TranslateAnchor(deltaX, deltaY, winPtr->anchor);
      x += point.x, y += point.y;
    }
    winPtr->winWidth = winWidth;
    winPtr->winHeight = winHeight;

    if ((x != Tk_X(winPtr->tkwin)) || (y != Tk_Y(winPtr->tkwin)) ||
      (winWidth != Tk_Width(winPtr->tkwin)) ||
      (winHeight != Tk_Height(winPtr->tkwin))) {
      Tk_MoveResizeWindow(winPtr->tkwin, x, y, winWidth, winHeight);
    }
    if (!Tk_IsMapped(winPtr->tkwin)) {
      Tk_MapWindow(winPtr->tkwin);
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * DrawPage --
 *
 *    This procedure displays the lines of text and moves the widgets
 *      to their new positions.  It draws lines with regard to
 *    the direction of the scrolling.  The idea here is to make the
 *    text and buttons appear to move together. Otherwise you will
 *    get a "jiggling" effect where the windows appear to bump into
 *    the next line before that line is moved.  In the worst case, where
 *    every line has at least one widget, you can get an aquarium effect
 *      (lines appear to ripple up).
 *
 *    The text area may start between line boundaries (to accommodate
 *    both variable height lines and constant scrolling). Subtract the
 *    difference of the page offset and the line offset from the starting
 *    coordinates. For horizontal scrolling, simply subtract the offset
 *    of the viewport. The window will clip the top of the first line,
 *    the bottom of the last line, whatever text extends to the left
 *    or right of the viewport on any line.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Commands are output to X to display the line in its current
 *    mode.
 *
 * ----------------------------------------------------------------------
 */
static void
DrawPage(htPtr, deltaY)
    HText *htPtr;
    int deltaY;               /* Change from previous Y coordinate */
{
    Line *linePtr;
    EmbeddedWidget *winPtr;
    Tk_Window tkwin = htPtr->tkwin;
    Segment sgmt;
    Pixmap pixmap;
    int forceCopy;
    int i;
    int lineNum;
    int x, y, lastY;
    Blt_ChainLink *linkPtr;
    int width, height;
    Display *display;

    display = htPtr->display;
    width = Tk_Width(tkwin);
    height = Tk_Height(tkwin);

    /* Create an off-screen pixmap for semi-smooth scrolling. */
    pixmap = Tk_GetPixmap(display, Tk_WindowId(tkwin), width, height,
        Tk_Depth(tkwin));

    x = -(htPtr->xOffset);
    y = -(htPtr->yOffset);

    if (htPtr->tile != NULL) {
      if (htPtr->tileOffsetPage) {
          Blt_SetTSOrigin(htPtr->tkwin, htPtr->tile, x, y);
      } else {
          Blt_SetTileOrigin(htPtr->tkwin, htPtr->tile, 0, 0);
      }
      Blt_TileRectangle(htPtr->tkwin, pixmap, htPtr->tile, 0, 0, width, 
            height);
    } else {
      XFillRectangle(display, pixmap, htPtr->fillGC, 0, 0, width, height);
    }


    if (deltaY >= 0) {
      y += htPtr->lineArr[htPtr->first].offset;
      lineNum = htPtr->first;
      lastY = 0;
    } else {
      y += htPtr->lineArr[htPtr->last].offset;
      lineNum = htPtr->last;
      lastY = height;
    }
    forceCopy = 0;

    /* Draw each line */
    for (i = htPtr->first; i <= htPtr->last; i++) {

      /* Initialize character position in text buffer to start */
      linePtr = htPtr->lineArr + lineNum;
      sgmt.textStart = linePtr->textStart;
      sgmt.textEnd = linePtr->textEnd;

      /* Initialize X position */
      x = -(htPtr->xOffset);
      for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          winPtr = Blt_ChainGetValue(linkPtr);

          if (winPtr->tkwin != NULL) {
            winPtr->flags |= WIDGET_VISIBLE;
            MoveEmbeddedWidget(winPtr, linePtr->offset);
          }
          sgmt.textEnd = winPtr->precedingTextEnd - 1;
          if (sgmt.textEnd >= sgmt.textStart) {
            DrawSegment(htPtr, pixmap, linePtr, x, y, &sgmt);
            x += winPtr->precedingTextWidth;
          }
          /* Skip over the extra trailing space which designates the widget */
          sgmt.textStart = winPtr->precedingTextEnd + 1;
          x += winPtr->cavityWidth;
          forceCopy++;
      }

      /*
       * This may be the text trailing the last widget or the entire
       * line if no widgets occur on it.
       */
      sgmt.textEnd = linePtr->textEnd;
      if (sgmt.textEnd >= sgmt.textStart) {
          DrawSegment(htPtr, pixmap, linePtr, x, y, &sgmt);
      }
      /* Go to the top of the next line */
      if (deltaY >= 0) {
          y += htPtr->lineArr[lineNum].height;
          lineNum++;
      }
      if ((forceCopy > 0) && !(htPtr->flags & TEXT_DIRTY)) {
          if (deltaY >= 0) {
            XCopyArea(display, pixmap, Tk_WindowId(tkwin), htPtr->drawGC,
                    0, lastY, width, y - lastY, 0, lastY);
          } else {
            XCopyArea(display, pixmap, Tk_WindowId(tkwin), htPtr->drawGC,
                    0, y, width, lastY - y, 0, y);
          }
          forceCopy = 0;      /* Reset drawing flag */
          lastY = y;          /* Record last Y position */
      }
      if ((deltaY < 0) && (lineNum > 0)) {
          --lineNum;
          y -= htPtr->lineArr[lineNum].height;
      }
    }
    /*
     * If the viewport was resized, draw the page in one operation.
     * Otherwise draw any left-over block of text (either at the top
     * or bottom of the page)
     */
    if (htPtr->flags & TEXT_DIRTY) {
      XCopyArea(display, pixmap, Tk_WindowId(tkwin),
          htPtr->drawGC, 0, 0, width, height, 0, 0);
    } else if (lastY != y) {
      if (deltaY >= 0) {
          height -= lastY;
          XCopyArea(display, pixmap, Tk_WindowId(tkwin),
            htPtr->drawGC, 0, lastY, width, height, 0, lastY);
      } else {
          height = lastY;
          XCopyArea(display, pixmap, Tk_WindowId(tkwin),
            htPtr->drawGC, 0, 0, width, height, 0, 0);
      }
    }
    Tk_FreePixmap(display, pixmap);
}


static void
SendBogusEvent(tkwin)
    Tk_Window tkwin;
{
#define DONTPROPAGATE 0
    XEvent event;

    event.type = event.xexpose.type = Expose;
    event.xexpose.window = Tk_WindowId(tkwin);
    event.xexpose.display = Tk_Display(tkwin);
    event.xexpose.count = 0;
    event.xexpose.x = event.xexpose.y = 0;
    event.xexpose.width = Tk_Width(tkwin);
    event.xexpose.height = Tk_Height(tkwin);

    XSendEvent(Tk_Display(tkwin), Tk_WindowId(tkwin), DONTPROPAGATE,
      ExposureMask, &event);
}

/*
 * ----------------------------------------------------------------------
 *
 * DisplayText --
 *
 *    This procedure is invoked to display a hypertext widget.
 *    Many of the operations which might ordinarily be performed
 *    elsewhere (e.g. in a configuration routine) are done here
 *    because of the somewhat unusual interactions occurring between
 *    the parent and embedded widgets.
 *
 *      Recompute the layout of the text if necessary. This is
 *    necessary if the world coordinate system has changed.
 *    Specifically, the following may have occurred:
 *
 *      1.  a text attribute has changed (font, linespacing, etc.).
 *      2.  widget option changed (anchor, width, height).
 *        3.  actual embedded widget was resized.
 *      4.  new text string or file.
 *
 *      This is deferred to the display routine since potentially
 *      many of these may occur (especially embedded widget changes).
 *
 *    Set the vertical and horizontal scrollbars (if they are
 *    designated) by issuing a Tcl command.  Done here since
 *    the text window width and height are needed.
 *
 *    If the viewport position or contents have changed in the
 *    vertical direction,  the now out-of-view embedded widgets
 *    must be moved off the viewport.  Since embedded widgets will
 *    obscure the text window, it is imperative that the widgets
 *    are moved off before we try to redraw text in the same area.
 *      This is necessary only for vertical movements.  Horizontal
 *    embedded widget movements are handled automatically in the
 *    page drawing routine.
 *
 *      Get the new first and last line numbers for the viewport.
 *      These line numbers may have changed because either a)
 *      the viewport changed size or position, or b) the text
 *    (embedded widget sizes or text attributes) have changed.
 *
 *    If the viewport has changed vertically (i.e. the first or
 *      last line numbers have changed), move the now out-of-view
 *    embedded widgets off the viewport.
 *
 *      Potentially many expose events may be generated when the
 *    the individual embedded widgets are moved and/or resized.
 *    These events need to be ignored.  Since (I think) expose
 *    events are guaranteed to happen in order, we can bracket
 *    them by sending phony events (via XSendEvent). The phony
 *      events turn on and off flags indicating which events
*     should be ignored.
 *
 *    Finally, the page drawing routine is called.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Commands are output to X to display the hypertext in its
 *    current mode.
 *
 * ----------------------------------------------------------------------
 */
static void
DisplayText(clientData)
    ClientData clientData;    /* Information about widget. */
{
    HText *htPtr = clientData;
    Tk_Window tkwin = htPtr->tkwin;
    int oldFirst;       /* First line of old viewport */
    int oldLast;        /* Last line of old viewport */
    int deltaY;               /* Change in viewport in Y direction */
    int reqWidth, reqHeight;

#ifdef notdef
    fprintf(stderr, "calling DisplayText(%s)\n", Tk_PathName(htPtr->tkwin));
#endif
    htPtr->flags &= ~REDRAW_PENDING;
    if (tkwin == NULL) {
      return;                 /* Window has been destroyed */
    }
    if (htPtr->flags & REQUEST_LAYOUT) {
      /*
       * Recompute the layout when widgets are created, deleted,
       * moved, or resized.  Also when text attributes (such as
       * font, linespacing) have changed.
       */
      ComputeLayout(htPtr);
    }
    htPtr->lastWidth = Tk_Width(tkwin);
    htPtr->lastHeight = Tk_Height(tkwin);

    /*
     * Check the requested width and height.  We allow two modes:
     *      1) If the user requested value is greater than zero, use it.
     *  2) Otherwise, let the window be as big as the virtual text.
     *         This could be too large to display, so constrain it by
     *         the maxWidth and maxHeight values.
     *
     * In any event, we need to calculate the size of the virtual
     * text and then make a geometry request.  This is so that widgets
     * whose size is relative to the master, will be set once.
     */
    if (htPtr->reqWidth > 0) {
      reqWidth = htPtr->reqWidth;
    } else {
      reqWidth = MIN(htPtr->worldWidth, htPtr->maxWidth);
      if (reqWidth < 1) {
          reqWidth = 1;
      }
    }
    if (htPtr->reqHeight > 0) {
      reqHeight = htPtr->reqHeight;
    } else {
      reqHeight = MIN(htPtr->worldHeight, htPtr->maxHeight);
      if (reqHeight < 1) {
          reqHeight = 1;
      }
    }
    if ((reqWidth != Tk_ReqWidth(tkwin)) || (reqHeight != Tk_ReqHeight(tkwin))) {
      Tk_GeometryRequest(tkwin, reqWidth, reqHeight);

      EventuallyRedraw(htPtr);
      return;                 /* Try again with new geometry */
    }
    if (!Tk_IsMapped(tkwin)) {
      return;
    }
    /*
     * Turn off layout requests here, after the text window has been
     * mapped.  Otherwise, relative embedded widget size requests wrt
     * to the size of parent text window will be wrong.
     */
    htPtr->flags &= ~REQUEST_LAYOUT;

    /* Is there a pending goto request? */
    if (htPtr->flags & GOTO_PENDING) {
      htPtr->pendingY = htPtr->lineArr[htPtr->reqLineNum].offset;
      htPtr->flags &= ~GOTO_PENDING;
    }
    deltaY = htPtr->pendingY - htPtr->yOffset;
    oldFirst = htPtr->first, oldLast = htPtr->last;

    /*
     * If the viewport has changed size or position, or the text
     * and/or embedded widgets have changed, adjust the scrollbars to
     * new positions.
     */
    if (htPtr->flags & TEXT_DIRTY) {
      int width, height;

      width = Tk_Width(htPtr->tkwin);
      height = Tk_Height(htPtr->tkwin);

      /* Reset viewport origin and world extents */
      htPtr->xOffset = Blt_AdjustViewport(htPtr->pendingX,
          htPtr->worldWidth, width,
          htPtr->xScrollUnits, BLT_SCROLL_MODE_LISTBOX);
      htPtr->yOffset = Blt_AdjustViewport(htPtr->pendingY,
          htPtr->worldHeight, height,
          htPtr->yScrollUnits, BLT_SCROLL_MODE_LISTBOX);
      if (htPtr->xScrollCmdPrefix != NULL) {
          Blt_UpdateScrollbar(htPtr->interp, htPtr->xScrollCmdPrefix,
            (double)htPtr->xOffset / htPtr->worldWidth,
            (double)(htPtr->xOffset + width) / htPtr->worldWidth);
      }
      if (htPtr->yScrollCmdPrefix != NULL) {
          Blt_UpdateScrollbar(htPtr->interp, htPtr->yScrollCmdPrefix,
            (double)htPtr->yOffset / htPtr->worldHeight,
            (double)(htPtr->yOffset + height) / htPtr->worldHeight);
      }
      /*
       * Given a new viewport or text height, find the first and
       * last line numbers of the new viewport.
       */
      if (GetVisibleLines(htPtr) != TCL_OK) {
          return;
      }
    }
    /*
     *      This is a kludge: Send an expose event before and after
     *      drawing the page of text.  Since moving and resizing of the
     *      embedded widgets will cause redundant expose events in the parent
     *      window, the phony events will bracket them indicating no
     *      action should be taken.
     */
    SendBogusEvent(tkwin);

    /*
     * If either the position of the viewport has changed or the size
     * of width or height of the entire text have changed, move the
     * widgets from the previous viewport out of the current
     * viewport. Worry only about the vertical embedded widget movements.
     * The page is always draw at full width and the viewport will clip
     * the text.
     */
    if ((htPtr->first != oldFirst) || (htPtr->last != oldLast)) {
      int offset;
      int i;
      int first, last;
      Blt_ChainLink *linkPtr;
      EmbeddedWidget *winPtr;

      /* Figure out which lines are now out of the viewport */

      if ((htPtr->first > oldFirst) && (htPtr->first <= oldLast)) {
          first = oldFirst, last = htPtr->first;
      } else if ((htPtr->last < oldLast) && (htPtr->last >= oldFirst)) {
          first = htPtr->last, last = oldLast;
      } else {
          first = oldFirst, last = oldLast;
      }

      for (i = first; i <= last; i++) {
          offset = htPtr->lineArr[i].offset;
          for (linkPtr = Blt_ChainFirstLink(htPtr->lineArr[i].chainPtr);
            linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
            winPtr = Blt_ChainGetValue(linkPtr);
            if (winPtr->tkwin != NULL) {
                MoveEmbeddedWidget(winPtr, offset);
                winPtr->flags &= ~WIDGET_VISIBLE;
            }
          }
      }
    }
    DrawPage(htPtr, deltaY);
    SendBogusEvent(tkwin);

    /* Reset flags */
    htPtr->flags &= ~TEXT_DIRTY;
}

/* Selection Procedures */
/*
 *----------------------------------------------------------------------
 *
 * TextSelectionProc --
 *
 *    This procedure is called back by Tk when the selection is
 *    requested by someone.  It returns part or all of the selection
 *    in a buffer provided by the caller.
 *
 * Results:
 *    The return value is the number of non-NULL bytes stored
 *    at buffer.  Buffer is filled (or partially filled) with a
 *    NULL-terminated string containing part or all of the selection,
 *    as given by offset and maxBytes.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */
static int
TextSelectionProc(clientData, offset, buffer, maxBytes)
    ClientData clientData;    /* Information about Text widget. */
    int offset;               /* Offset within selection of first
                         * character to be returned. */
    char *buffer;       /* Location in which to place
                         * selection. */
    int maxBytes;       /* Maximum number of bytes to place
                         * at buffer, not including terminating
                         * NULL character. */
{
    HText *htPtr = clientData;
    int size;

    if ((htPtr->selFirst < 0) || (!htPtr->exportSelection)) {
      return -1;
    }
    size = (htPtr->selLast - htPtr->selFirst) + 1 - offset;
    if (size > maxBytes) {
      size = maxBytes;
    }
    if (size <= 0) {
      return 0;         /* huh? */
    }
    strncpy(buffer, htPtr->charArr + htPtr->selFirst + offset, size);
    buffer[size] = '\0';
    return size;
}

/*
 *----------------------------------------------------------------------
 *
 * TextLostSelection --
 *
 *    This procedure is called back by Tk when the selection is
 *    grabbed away from a Text widget.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The existing selection is unhighlighted, and the window is
 *    marked as not containing a selection.
 *
 *----------------------------------------------------------------------
 */
static void
TextLostSelection(clientData)
    ClientData clientData;    /* Information about Text widget. */
{
    HText *htPtr = clientData;

    if ((htPtr->selFirst >= 0) && (htPtr->exportSelection)) {
      htPtr->selFirst = htPtr->selLast = -1;
      EventuallyRedraw(htPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SelectLine --
 *
 *    Modify the selection by moving both its anchored and un-anchored
 *    ends.  This could make the selection either larger or smaller.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
SelectLine(htPtr, tindex)
    HText *htPtr;       /* Information about widget. */
    int tindex;               /* Index of element that is to
                         * become the "other" end of the
                         * selection. */
{
    int selFirst, selLast;
    int lineNum;
    Line *linePtr;

    lineNum = IndexSearch(htPtr, tindex, 0, htPtr->nLines - 1);
    if (lineNum < 0) {
      char string[200];

      sprintf(string, "can't determine line number from index \"%d\"",
          tindex);
      Tcl_AppendResult(htPtr->interp, string, (char *)NULL);
      return TCL_ERROR;
    }
    linePtr = htPtr->lineArr + lineNum;
    /*
     * Grab the selection if we don't own it already.
     */
    if ((htPtr->exportSelection) && (htPtr->selFirst == -1)) {
      Tk_OwnSelection(htPtr->tkwin, XA_PRIMARY, TextLostSelection, htPtr);
    }
    selFirst = linePtr->textStart;
    selLast = linePtr->textEnd;
    htPtr->selAnchor = tindex;
    if ((htPtr->selFirst != selFirst) ||
      (htPtr->selLast != selLast)) {
      htPtr->selFirst = selFirst;
      htPtr->selLast = selLast;
      EventuallyRedraw(htPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectWord --
 *
 *    Modify the selection by moving both its anchored and un-anchored
 *    ends.  This could make the selection either larger or smaller.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
SelectWord(htPtr, tindex)
    HText *htPtr;       /* Information about widget. */
    int tindex;               /* Index of element that is to
                         * become the "other" end of the
                         * selection. */
{
    int selFirst, selLast;
    int i;

    for (i = tindex; i < htPtr->nChars; i++) {
      if (isspace(UCHAR(htPtr->charArr[i]))) {
          break;
      }
    }
    selLast = i - 1;
    for (i = tindex; i >= 0; i--) {
      if (isspace(UCHAR(htPtr->charArr[i]))) {
          break;
      }
    }
    selFirst = i + 1;
    if (selFirst > selLast) {
      selFirst = selLast = tindex;
    }
    /*
     * Grab the selection if we don't own it already.
     */
    if ((htPtr->exportSelection) && (htPtr->selFirst == -1)) {
      Tk_OwnSelection(htPtr->tkwin, XA_PRIMARY, TextLostSelection, htPtr);
    }
    htPtr->selAnchor = tindex;
    if ((htPtr->selFirst != selFirst) || (htPtr->selLast != selLast)) {
      htPtr->selFirst = selFirst, htPtr->selLast = selLast;
      EventuallyRedraw(htPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectTextBlock --
 *
 *    Modify the selection by moving its un-anchored end.  This
 *    could make the selection either larger or smaller.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
SelectTextBlock(htPtr, tindex)
    HText *htPtr;       /* Information about widget. */
    int tindex;               /* Index of element that is to
                         * become the "other" end of the
                         * selection. */
{
    int selFirst, selLast;

    /*
     * Grab the selection if we don't own it already.
     */

    if ((htPtr->exportSelection) && (htPtr->selFirst == -1)) {
      Tk_OwnSelection(htPtr->tkwin, XA_PRIMARY, TextLostSelection, htPtr);
    }
    /*  If the anchor hasn't been set yet, assume the beginning of the text*/
    if (htPtr->selAnchor < 0) {
      htPtr->selAnchor = 0;
    }
    if (htPtr->selAnchor <= tindex) {
      selFirst = htPtr->selAnchor;
      selLast = tindex;
    } else {
      selFirst = tindex;
      selLast = htPtr->selAnchor;
    }
    if ((htPtr->selFirst != selFirst) || (htPtr->selLast != selLast)) {
      htPtr->selFirst = selFirst, htPtr->selLast = selLast;
      EventuallyRedraw(htPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SelectOp --
 *
 *    This procedure handles the individual options for text
 *    selections.  The selected text is designated by start and end
 *    indices into the text pool.  The selected segment has both a
 *    anchored and unanchored ends.  The following selection
 *    operations are implemented:
 *
 *      "adjust"  - resets either the first or last index
 *                  of the selection.
 *      "clear"   - clears the selection. Sets first/last
 *                  indices to -1.
 *      "from"    - sets the index of the selection anchor.
 *      "line"    - sets the first of last indices to the
 *                  start and end of the line at the
 *                  designated point.
 *      "present" - return "1" if a selection is available,
 *                  "0" otherwise.
 *      "range"   - sets the first and last indices.
 *      "to"            - sets the index of the un-anchored end.
 *      "word"    - sets the first of last indices to the
 *                  start and end of the word at the
 *                  designated point.
 * Results:
 *    None.
 *
 * Side effects:
 *    The selection changes.
 *
 *----------------------------------------------------------------------
 */
static int
SelectOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int iselection;
    unsigned int length;
    int result = TCL_OK;
    char c;

    length = strlen(argv[2]);
    c = argv[2][0];
    if ((c == 'c') && (strncmp(argv[2], "clear", length) == 0)) {
      if (argc != 3) {
          Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
            " selection clear\"", (char *)NULL);
          return TCL_ERROR;
      }
      if (htPtr->selFirst != -1) {
          htPtr->selFirst = htPtr->selLast = -1;
          EventuallyRedraw(htPtr);
      }
      return TCL_OK;
    } else if ((c == 'p') && (strncmp(argv[2], "present", length) == 0)) {
      if (argc != 3) {
          Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
            " selection present\"", (char *)NULL);
          return TCL_ERROR;
      }
      Tcl_AppendResult(interp, (htPtr->selFirst != -1) ? "0" : "1",
          (char *)NULL);
      return TCL_OK;
    } else if ((c == 'r') && (strncmp(argv[2], "range", length) == 0)) {
      int selFirst, selLast;

      if (argc != 5) {
          Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
            " selection range first last\"", (char *)NULL);
          return TCL_ERROR;
      }
      if (GetIndex(htPtr, argv[3], &selFirst) != TCL_OK) {
          return TCL_ERROR;
      }
      if (GetIndex(htPtr, argv[4], &selLast) != TCL_OK) {
          return TCL_ERROR;
      }
      htPtr->selAnchor = selFirst;
      SelectTextBlock(htPtr, selLast);
      return TCL_OK;
    }
    if (argc != 4) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          " selection ", argv[2], " index\"", (char *)NULL);
      return TCL_ERROR;
    }
    if (GetIndex(htPtr, argv[3], &iselection) != TCL_OK) {
      return TCL_ERROR;
    }
    if ((c == 'f') && (strncmp(argv[2], "from", length) == 0)) {
      htPtr->selAnchor = iselection;
    } else if ((c == 'a') && (strncmp(argv[2], "adjust", length) == 0)) {
      int half1, half2;

      half1 = (htPtr->selFirst + htPtr->selLast) / 2;
      half2 = (htPtr->selFirst + htPtr->selLast + 1) / 2;
      if (iselection < half1) {
          htPtr->selAnchor = htPtr->selLast;
      } else if (iselection > half2) {
          htPtr->selAnchor = htPtr->selFirst;
      }
      result = SelectTextBlock(htPtr, iselection);
    } else if ((c == 't') && (strncmp(argv[2], "to", length) == 0)) {
      result = SelectTextBlock(htPtr, iselection);
    } else if ((c == 'w') && (strncmp(argv[2], "word", length) == 0)) {
      result = SelectWord(htPtr, iselection);
    } else if ((c == 'l') && (strncmp(argv[2], "line", length) == 0)) {
      result = SelectLine(htPtr, iselection);
    } else {
      Tcl_AppendResult(interp, "bad selection operation \"", argv[2],
          "\": should be \"adjust\", \"clear\", \"from\", \"line\", \
\"present\", \"range\", \"to\", or \"word\"", (char *)NULL);
      return TCL_ERROR;
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * GotoOp --
 *
 *    Move the top line of the viewport to the new location based
 *    upon the given line number.  Force out-of-range requests to the
 *    top or bottom of text.
 *
 * Results:
 *    A standard Tcl result. If TCL_OK, interp->result contains the
 *    current line number.
 *
 * Side effects:
 *    At the next idle point, the text viewport will be move to the
 *    new line.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
GotoOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;       /* Not used. */
    int argc;
    char **argv;
{
    int line;

    line = htPtr->first;
    if (argc == 3) {
      int tindex;

      if (GetIndex(htPtr, argv[2], &tindex) != TCL_OK) {
          return TCL_ERROR;
      }
      line = IndexSearch(htPtr, tindex, 0, htPtr->nLines - 1);
      if (line < 0) {
          char string[200];

          sprintf(string, "can't determine line number from index \"%d\"",
            tindex);
          Tcl_AppendResult(htPtr->interp, string, (char *)NULL);
          return TCL_ERROR;
      }
      htPtr->reqLineNum = line;
      htPtr->flags |= TEXT_DIRTY;

      /*
       * Make only a request for a change in the viewport.  Defer
       * the actual scrolling until the text layout is adjusted at
       * the next idle point.
       */
      if (line != htPtr->first) {
          htPtr->flags |= GOTO_PENDING;
          EventuallyRedraw(htPtr);
      }
    }
    Tcl_SetResult(htPtr->interp, Blt_Itoa(line), TCL_VOLATILE);
    return TCL_OK;
}


static int
XViewOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int width, worldWidth;

    width = Tk_Width(htPtr->tkwin);
    worldWidth = htPtr->worldWidth;
    if (argc == 2) {
      double fract;

      /* Report first and last fractions */
      fract = (double)htPtr->xOffset / worldWidth;
      Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
      fract = (double)(htPtr->xOffset + width) / worldWidth;
      Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
      return TCL_OK;
    }
    htPtr->pendingX = htPtr->xOffset;
    if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(htPtr->pendingX),
          worldWidth, width, htPtr->xScrollUnits, BLT_SCROLL_MODE_LISTBOX) 
      != TCL_OK) {
      return TCL_ERROR;
    }
    htPtr->flags |= TEXT_DIRTY;
    EventuallyRedraw(htPtr);
    return TCL_OK;
}

static int
YViewOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int height, worldHeight;

    height = Tk_Height(htPtr->tkwin);
    worldHeight = htPtr->worldHeight;
    if (argc == 2) {
      double fract;

      /* Report first and last fractions */
      fract = (double)htPtr->yOffset / worldHeight;
      Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
      fract = (double)(htPtr->yOffset + height) / worldHeight;
      Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
      return TCL_OK;
    }
    htPtr->pendingY = htPtr->yOffset;
    if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(htPtr->pendingY),
          worldHeight, height, htPtr->yScrollUnits, BLT_SCROLL_MODE_LISTBOX)
      != TCL_OK) {
      return TCL_ERROR;
    }
    htPtr->flags |= TEXT_DIRTY;
    EventuallyRedraw(htPtr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * AppendOp --
 *
 *    This procedure embeds a Tk widget into the hypertext.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    Memory is allocated.  EmbeddedWidget gets configured.
 *
 * ----------------------------------------------------------------------
 */
static int
AppendOp(htPtr, interp, argc, argv)
    HText *htPtr;       /* Hypertext widget */
    Tcl_Interp *interp;       /* Interpreter associated with widget */
    int argc;                 /* Number of arguments. */
    char **argv;        /* Argument strings. */
{
    Line *linePtr;
    EmbeddedWidget *winPtr;

    winPtr = CreateEmbeddedWidget(htPtr, argv[2]);
    if (winPtr == NULL) {
      return TCL_ERROR;
    }
    if (Tk_ConfigureWidget(interp, htPtr->tkwin, widgetConfigSpecs,
          argc - 3, argv + 3, (char *)winPtr, 0) != TCL_OK) {
      return TCL_ERROR;
    }
    /*
     * Append widget to list of embedded widgets of the last line.
     */
    linePtr = GetLastLine(htPtr);
    if (linePtr == NULL) {
      Tcl_AppendResult(htPtr->interp, "can't allocate line structure",
          (char *)NULL);
      return TCL_ERROR;
    }
    Blt_ChainAppend(linePtr->chainPtr, winPtr);
    linePtr->width += winPtr->cavityWidth;
    winPtr->precedingTextEnd = linePtr->textEnd;

    htPtr->flags |= (WIDGET_APPENDED | REQUEST_LAYOUT);
    EventuallyRedraw(htPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * WindowsOp --
 *
 *    Returns a list of all the pathNames of embedded widgets of the
 *    HText widget.  If a pattern argument is given, only the names
 *    of windows matching it will be placed into the list.
 *
 * Results:
 *    Standard Tcl result.  If TCL_OK, interp->result will contain
 *    the list of the embedded widget pathnames.  Otherwise it will
 *    contain an error message.
 *
 *----------------------------------------------------------------------
 */
static int
WindowsOp(htPtr, interp, argc, argv)
    HText *htPtr;       /* Hypertext widget record */
    Tcl_Interp *interp;       /* Interpreter associated with widget */
    int argc;
    char **argv;
{
    EmbeddedWidget *winPtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    char *name;

    for (hPtr = Blt_FirstHashEntry(&(htPtr->widgetTable), &cursor);
      hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
      winPtr = (EmbeddedWidget *)Blt_GetHashValue(hPtr);
      if (winPtr->tkwin == NULL) {
          fprintf(stderr, "window `%s' is null\n",
            Tk_PathName(Blt_GetHashKey(&(htPtr->widgetTable), hPtr)));
          continue;
      }
      name = Tk_PathName(winPtr->tkwin);
      if ((argc == 2) || (Tcl_StringMatch(name, argv[2]))) {
          Tcl_AppendElement(interp, name);
      }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CgetOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    char *itemPtr;
    Tk_ConfigSpec *specsPtr;

    if ((argc > 2) && (argv[2][0] == '.')) {
      Tk_Window tkwin;
      EmbeddedWidget *winPtr;

      /* EmbeddedWidget window to be configured */
      tkwin = Tk_NameToWindow(interp, argv[2], htPtr->tkwin);
      if (tkwin == NULL) {
          return TCL_ERROR;
      }
      winPtr = FindEmbeddedWidget(htPtr, tkwin);
      if (winPtr == NULL) {
          Tcl_AppendResult(interp, "window \"", argv[2],
            "\" is not managed by \"", argv[0], "\"", (char *)NULL);
          return TCL_ERROR;
      }
      specsPtr = widgetConfigSpecs;
      itemPtr = (char *)winPtr;
      argv++;
    } else {
      specsPtr = configSpecs;
      itemPtr = (char *)htPtr;
    }
    return Tk_ConfigureValue(interp, htPtr->tkwin, specsPtr, itemPtr,
          argv[2], 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *    This procedure is called to process an argv/argc list, plus
 *    the Tk option database, in order to configure (or reconfigure)
 *    a hypertext widget.
 *
 * Results:
 *    A standard Tcl result.  If TCL_ERROR is returned, then
 *    interp->result contains an error message.
 *
 * Side effects:
 *    Configuration information, such as text string, colors, font,
 *    etc. get set for htPtr;  old resources get freed, if there were any.
 *    The hypertext is redisplayed.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    char *itemPtr;
    Tk_ConfigSpec *specsPtr;

    if ((argc > 2) && (argv[2][0] == '.')) {
      Tk_Window tkwin;
      EmbeddedWidget *winPtr;

      /* EmbeddedWidget window to be configured */
      tkwin = Tk_NameToWindow(interp, argv[2], htPtr->tkwin);
      if (tkwin == NULL) {
          return TCL_ERROR;
      }
      winPtr = FindEmbeddedWidget(htPtr, tkwin);
      if (winPtr == NULL) {
          Tcl_AppendResult(interp, "window \"", argv[2],
            "\" is not managed by \"", argv[0], "\"", (char *)NULL);
          return TCL_ERROR;
      }
      specsPtr = widgetConfigSpecs;
      itemPtr = (char *)winPtr;
      argv++;
      argc--;
    } else {
      specsPtr = configSpecs;
      itemPtr = (char *)htPtr;
    }
    if (argc == 2) {
      return Tk_ConfigureInfo(interp, htPtr->tkwin, specsPtr, itemPtr,
            (char *)NULL, 0);
    } else if (argc == 3) {
      return Tk_ConfigureInfo(interp, htPtr->tkwin, specsPtr, itemPtr,
            argv[2], 0);
    }
    if (Tk_ConfigureWidget(interp, htPtr->tkwin, specsPtr, argc - 2,
          argv + 2, itemPtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
      return TCL_ERROR;
    }
    if (itemPtr == (char *)htPtr) {
      /* Reconfigure the master */
      if (ConfigureText(interp, htPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    } else {
      htPtr->flags |= REQUEST_LAYOUT;
    }
    EventuallyRedraw(htPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ScanOp --
 *
 *    Implements the quick scan for hypertext widgets.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ScanOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    int x, y;
    char c;
    unsigned int length;


    if (Blt_GetXY(interp, htPtr->tkwin, argv[3], &x, &y) != TCL_OK) {
      return TCL_ERROR;
    }
    c = argv[2][0];
    length = strlen(argv[2]);
    if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
      htPtr->scanMark.x = x, htPtr->scanMark.y = y;
      htPtr->scanPt.x = htPtr->xOffset;
      htPtr->scanPt.y = htPtr->yOffset;

    } else if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
      int px, py;

      px = htPtr->scanPt.x - (10 * (x - htPtr->scanMark.x));
      py = htPtr->scanPt.y - (10 * (y - htPtr->scanMark.y));

      if (px < 0) {
          px = htPtr->scanPt.x = 0;
          htPtr->scanMark.x = x;
      } else if (px >= htPtr->worldWidth) {
          px = htPtr->scanPt.x = htPtr->worldWidth - htPtr->xScrollUnits;
          htPtr->scanMark.x = x;
      }
      if (py < 0) {
          py = htPtr->scanPt.y = 0;
          htPtr->scanMark.y = y;
      } else if (py >= htPtr->worldHeight) {
          py = htPtr->scanPt.y = htPtr->worldHeight - htPtr->yScrollUnits;
          htPtr->scanMark.y = y;
      }
      if ((py != htPtr->pendingY) || (px != htPtr->pendingX)) {
          htPtr->pendingX = px, htPtr->pendingY = py;
          htPtr->flags |= TEXT_DIRTY;
          EventuallyRedraw(htPtr);
      }
    } else {
      Tcl_AppendResult(interp, "bad scan operation \"", argv[2],
          "\": should be either \"mark\" or \"dragto\"", (char *)NULL);
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SearchOp --
 *
 *    Returns the linenumber of the next line matching the given
 *    pattern within the range of lines provided.  If the first
 *    line number is greater than the last, the search is done in
 *    reverse.
 *
 *----------------------------------------------------------------------
 */
static int
SearchOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    char *startPtr, *endPtr;
    char saved;
    Tcl_RegExp regExpToken;
    int iFirst, iLast;
    int matchStart, matchEnd;
    int match;

    regExpToken = Tcl_RegExpCompile(interp, argv[2]);
    if (regExpToken == NULL) {
      return TCL_ERROR;
    }
    iFirst = 0;
    iLast = htPtr->nChars;
    if (argc > 3) {
      if (GetIndex(htPtr, argv[3], &iFirst) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (argc == 4) {
      if (GetIndex(htPtr, argv[4], &iLast) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (iLast < iFirst) {
      return TCL_ERROR;
    }
    matchStart = matchEnd = -1;
    startPtr = htPtr->charArr + iFirst;
    endPtr = htPtr->charArr + (iLast + 1);
    saved = *endPtr;
    *endPtr = '\0';           /* Make the line a string by changing the
                         * '\n' into a NUL byte before searching */
    match = Tcl_RegExpExec(interp, regExpToken, startPtr, startPtr);
    *endPtr = saved;
    if (match < 0) {
      return TCL_ERROR;
    } else if (match > 0) {
      Tcl_RegExpRange(regExpToken, 0, &startPtr, &endPtr);
      if ((startPtr != NULL) || (endPtr != NULL)) {
          matchStart = startPtr - htPtr->charArr;
          matchEnd = endPtr - htPtr->charArr - 1;
      }
    }
    if (match > 0) {
      Tcl_AppendElement(interp, Blt_Itoa(matchStart));
      Tcl_AppendElement(interp, Blt_Itoa(matchEnd));
    } else {
      Tcl_ResetResult(interp);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RangeOp --
 *
 *    Returns the characters designated by the range of elements.
 *
 *----------------------------------------------------------------------
 */
static int
RangeOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    char *startPtr, *endPtr;
    char saved;
    int textFirst, textLast;

    textFirst = htPtr->selFirst;
    textLast = htPtr->selLast;
    if (textFirst < 0) {
      textFirst = 0;
      textLast = htPtr->nChars - 1;
    }
    if (argc > 2) {
      if (GetIndex(htPtr, argv[2], &textFirst) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (argc == 4) {
      if (GetIndex(htPtr, argv[3], &textLast) != TCL_OK) {
          return TCL_ERROR;
      }
    }
    if (textLast < textFirst) {
      Tcl_AppendResult(interp, "first index is greater than last", (char *)NULL);
      return TCL_ERROR;
    }
    startPtr = htPtr->charArr + textFirst;
    endPtr = htPtr->charArr + (textLast + 1);
    saved = *endPtr;
    *endPtr = '\0';           /* Make the line into a string by
                         * changing the * '\n' into a '\0'
                         * before copying */
    Tcl_SetResult(interp, startPtr, TCL_VOLATILE);
    *endPtr = saved;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * IndexOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
IndexOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    int tindex;

    if (GetIndex(htPtr, argv[2], &tindex) != TCL_OK) {
      return TCL_ERROR;
    }
    Tcl_SetResult(interp, Blt_Itoa(tindex), TCL_VOLATILE);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * LinePosOp --
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
LinePosOp(htPtr, interp, argc, argv)
    HText *htPtr;
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    int line, cpos, tindex;
    char string[200];

    if (GetIndex(htPtr, argv[2], &tindex) != TCL_OK) {
      return TCL_ERROR;
    }
    if (GetTextPosition(htPtr, tindex, &line, &cpos) != TCL_OK) {
      return TCL_ERROR;
    }
    sprintf(string, "%d.%d", line, cpos);
    Tcl_SetResult(interp, string, TCL_VOLATILE);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------
 *
 * TextWidgetCmd --
 *
 *    This procedure is invoked to process the Tcl command that
 *    corresponds to a widget managed by this module. See the user
 *    documentation for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 * --------------------------------------------------------------
 */

static Blt_OpSpec textOps[] =
{
    {"append", 1, (Blt_Op)AppendOp, 3, 0, "window ?option value?...",},
    {"cget", 2, (Blt_Op)CgetOp, 3, 3, "?window? option",},
    {"configure", 2, (Blt_Op)ConfigureOp, 2, 0,
      "?window? ?option value?...",},
    {"gotoline", 2, (Blt_Op)GotoOp, 2, 3, "?line?",},
    {"index", 1, (Blt_Op)IndexOp, 3, 3, "string",},
    {"linepos", 1, (Blt_Op)LinePosOp, 3, 3, "string",},
    {"range", 2, (Blt_Op)RangeOp, 2, 4, "?from? ?to?",},
    {"scan", 2, (Blt_Op)ScanOp, 4, 4, "oper @x,y",},
    {"search", 3, (Blt_Op)SearchOp, 3, 5, "pattern ?from? ?to?",},
    {"selection", 3, (Blt_Op)SelectOp, 3, 5, "oper ?index?",},
    {"windows", 6, (Blt_Op)WindowsOp, 2, 3, "?pattern?",},
    {"xview", 1, (Blt_Op)XViewOp, 2, 5,
      "?moveto fract? ?scroll number what?",},
    {"yview", 1, (Blt_Op)YViewOp, 2, 5,
      "?moveto fract? ?scroll number what?",},
};
static int nTextOps = sizeof(textOps) / sizeof(Blt_OpSpec);

static int
TextWidgetCmd(clientData, interp, argc, argv)
    ClientData clientData;    /* Information about hypertext widget. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    char **argv;        /* Argument strings. */
{
    Blt_Op proc;
    int result;
    HText *htPtr = clientData;

    proc = Blt_GetOp(interp, nTextOps, textOps, BLT_OP_ARG1, argc, argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    Tcl_Preserve(htPtr);
    result = (*proc) (htPtr, interp, argc, argv);
    Tcl_Release(htPtr);
    return result;
}

/*
 * --------------------------------------------------------------
 *
 * TextCmd --
 *
 *    This procedure is invoked to process the "htext" Tcl command.
 *    See the user documentation for details on what it does.
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side effects:
 *    See the user documentation.
 *
 * --------------------------------------------------------------
 */
/* ARGSUSED */
static int
TextCmd(clientData, interp, argc, argv)
    ClientData clientData;    /* Main window associated with interpreter. */
    Tcl_Interp *interp;       /* Current interpreter. */
    int argc;                 /* Number of arguments. */
    char **argv;        /* Argument strings. */
{
    HText *htPtr;
    Screen *screenPtr;
    Tk_Window tkwin;

    if (argc < 2) {
      Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          " pathName ?option value?...\"", (char *)NULL);
      return TCL_ERROR;
    }
    htPtr = Blt_Calloc(1, sizeof(HText));
    assert(htPtr);
    tkwin = Tk_MainWindow(interp);
    tkwin = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *)NULL);
    if (tkwin == NULL) {
      Blt_Free(htPtr);
      return TCL_ERROR;
    }
    /* Initialize the new hypertext widget */

    Tk_SetClass(tkwin, "Htext");
    htPtr->tkwin = tkwin;
    htPtr->display = Tk_Display(tkwin);
    htPtr->interp = interp;
    htPtr->nLines = htPtr->arraySize = 0;
    htPtr->leader = 1;
    htPtr->xScrollUnits = htPtr->yScrollUnits = 10;
    htPtr->nRows = htPtr->nColumns = 0;
    htPtr->selFirst = htPtr->selLast = -1;
    htPtr->selAnchor = 0;
    htPtr->exportSelection = TRUE;
    htPtr->selBorderWidth = 2;
    screenPtr = Tk_Screen(htPtr->tkwin);
    htPtr->maxWidth = WidthOfScreen(screenPtr);
    htPtr->maxHeight = HeightOfScreen(screenPtr);
    Blt_InitHashTable(&(htPtr->widgetTable), BLT_ONE_WORD_KEYS);

    Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING, TextSelectionProc,
      htPtr, XA_STRING);
    Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask,
      TextEventProc, htPtr);
#if (TK_MAJOR_VERSION > 4)
    Blt_SetWindowInstanceData(tkwin, htPtr);
#endif
    /*
     * -----------------------------------------------------------------
     *
     *  Create the widget command before configuring the widget. This
     *  is because the "-file" and "-text" options may have embedded
     *  commands that self-reference the widget through the
     *  "$blt_htext(widget)" variable.
     *
     * ------------------------------------------------------------------
     */
    htPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], TextWidgetCmd, htPtr, 
      TextDeleteCmdProc);
#ifdef ITCL_NAMESPACES
    Itk_SetWidgetCommand(htPtr->tkwin, htPtr->cmdToken);
#endif
    if ((Tk_ConfigureWidget(interp, htPtr->tkwin, configSpecs, argc - 2,
            argv + 2, (char *)htPtr, 0) != TCL_OK) ||
      (ConfigureText(interp, htPtr) != TCL_OK)) {
      Tk_DestroyWindow(htPtr->tkwin);
      return TCL_ERROR;
    }
    Tcl_SetResult(interp, Tk_PathName(htPtr->tkwin), TCL_VOLATILE);
    return TCL_OK;
}

int
Blt_HtextInit(interp)
    Tcl_Interp *interp;
{
    static Blt_CmdSpec cmdSpec =
    {"htext", TextCmd,};

    if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) {
      return TCL_ERROR;
    }
    return TCL_OK;
}

#endif /* NO_HTEXT */

Generated by  Doxygen 1.6.0   Back to index