blob: ffd9e7574a27a50dd97928651b35a4ab984cb212 [file] [log] [blame]
/*
* htext.c --
*
* Copyright 1990 Regents of the University of California.
* 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. The University of California
* makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without
* express or implied warranty.
*
* Copyright 1991,1992 by AT&T Bell Laboratories.
* 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 AT&T Bell Laboratories any of their entities not be used
* in advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* AT&T disclaims all warranties with regard to this software, including
* all implied warranties of merchantability and fitness. In no event
* shall AT&T 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
* tortious action, arising out of or in connection with the use or
* performance of this software.
*
* Hypertext widget created by George Howlett.
*/
/*
* To do:
*
* 1) Fix scroll unit round off errors.
*
* 2) Bug in reporting errors in Tcl evaluations.
*
* 3) Selections of text. (characters, word, line)
*
* 4) Tabstops for easier placement of text and child widgets.
* Use variable "tabstops" to set/reset tabstops.
*
* 5) Better error checking.
*
*/
#include <config.h>
#include <stdio.h>
#if STDC_HEADERS
#include <stdlib.h>
#endif
#if STDC_HEADERS
#include <string.h>
#endif
#include <errno.h>
#include <tcl.h>
#include <tk.h>
#include <X11/Xutil.h>
#define TRUE 1
#define FALSE 0
#define NULLSTR(s) ((s)==NULL || *(s)=='\0')
#define LINES_ALLOC_CHUNK 512
#define MAX_WIN_SIZE 3000
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#ifdef DEBUG
#include <malloc.h>
#endif
#ifdef __GNUG__
#define INLINE inline
#else
#define INLINE
#endif
/*
* Flags passed to TkMeasureChars: taken from tkInt.h
*/
#define TK_WHOLE_WORDS 1
#define TK_AT_LEAST_ONE 2
#define TK_PARTIAL_OK 4
#define BLACK "Black"
#define WHITE "White"
#define BISQUE1 "#ffe4c4"
#define BISQUE2 "#eed5b7"
#define BISQUE3 "#cdb79e"
#define LIGHTBLUE2 "#b2dfee"
#define LIGHTPINK1 "#ffaeb9"
#define MAROON "#b03060"
/*
* Flag bits children subwindows:
*
* VISIBLE: Child is in the viewport.
* MAPPED: Child is mapped on the screen.
*/
#define MAPPED 2
#define VISIBLE 4
/*
* Flag bits for the hypertext widget:
*
* REDRAW_PENDING: Non-zero means a DoWhenIdle handler has already been
* queued to redraw this window.
*
* LAYOUT_NEEDED: Something has happened which requires the layout of
* the text and child window positions to be recalculated.
* The following actions may cause this:
*
* - the contents of the hypertext has changed by
* either the -file or -text options.
* - a text attribute has changed (line spacing, font, etc)
* - a subwindow has changed (resized or moved).
* - a child configuration option has changed.
*
* LAYOUT_CHANGED: The layout was recalculated and the size of the world
* (text layout) has changed.
*
* VIEWPORT_MOVED: The position of the viewport has moved. The occurs
* when scrolling or goto-ing a new line.
*
* VIEWPORT_RESIZED: The size of the viewport (i.e. window size) has changed.
*
* IGNORE_EXPOSURES: Ignore exposure events in the text window. Potentially
* many expose events may occur when unmapping, rearranging,
* or resizing child subwindows during a single call to
* the text display routine. This flag is examined in the
* event handler to determine if a call to the display
* routine is necessary.
*
* GOTO_PENDING: Non-zero means that the widget should move to a new
* starting line number when redrawing.
*
*/
#define REDRAW_PENDING 1
#define IGNORE_EXPOSURES 2
#define VIEWPORT_RESIZED 4
#define VIEWPORT_MOVED 8
#define LAYOUT_NEEDED 0x10
#define LAYOUT_CHANGED 0x20
#define GOTO_PENDING 0x40
typedef struct Child {
/* private: */
struct Hypertext *parent;/* Parent widget */
Tk_Window tkwin; /* Widget window */
int flags;
int x, y; /* Origin of child subwindow */
short int width, height; /* Size of child region */
int precedingTextEnd; /* Number of characters of text */
int precedingTextWidth; /* Width of normal text preceding child */
struct Child *nextPtr; /* Next child in list */
/* public: */
int widthWanted; /* Constraint for width */
int heightWanted; /* Constraint for height */
int padX; /* Pad to child width */
int padY; /* Pad to height */
Tk_Anchor anchor;
} Child;
/*
* Information used for parsing configuration specs:
*/
/*
* Defaults for children:
*/
#define DEF_CHILD_WIDTH_WANTED "0"
#define DEF_CHILD_HEIGHT_WANTED "0"
#define DEF_CHILD_ANCHOR "center"
#define DEF_CHILD_PADX "0"
#define DEF_CHILD_PADY "0"
static Tk_ConfigSpec childConfigSpecs[]=
{
{TK_CONFIG_ANCHOR, "-anchor", "anchor", "anchor",
DEF_CHILD_ANCHOR, Tk_Offset (Child, anchor), 0},
{TK_CONFIG_PIXELS, "-height", "height", "Height",
DEF_CHILD_HEIGHT_WANTED, Tk_Offset (Child, heightWanted), 0},
{TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
DEF_CHILD_PADX, Tk_Offset (Child, padX), 0},
{TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
DEF_CHILD_PADY, Tk_Offset (Child, padY), 0},
{TK_CONFIG_PIXELS, "-width", "width", "Width",
DEF_CHILD_WIDTH_WANTED, Tk_Offset (Child, widthWanted), 0},
{TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
(char *)NULL, 0, 0}
};
/*
* Structure to contain the contents of a single line of text and the
* children on that line.
*
* Individual lines are not configureable, although changes to the
* size of children do effect its values.
*/
typedef struct {
/* private: */
int offset; /* offset (in pixels) from world coordinate 0,0 */
short int height; /* Height of line */
short int width; /* Width of line */
short int baseline; /* Baseline of text */
short int textLength; /* Number of characters in normal text */
char *text; /* The plain text on the line */
Child *children; /* List of children for the line */
Child *lastChild; /* Last child in list */
} Line;
/*
* Hypertext widget.
*/
typedef struct Hypertext {
/* private: */
Tk_Window tkwin; /* Window that embodies the child. NULL means
* that the window has been destroyed. */
Tcl_Interp *interp; /* Interpreter associated with child. */
int flags;
GC gc; /* Graphics context for normal text */
Tk_3DBorder border; /* used ?? */
XColor *normalFg;
Line **lineArr; /* Array of text lines */
int numLines; /* Number of filled entries in array */
int arraySize; /* Number of entries allocated for array */
int height; /* Height of text in pixels */
int width; /* Width of text in pixels */
/*
* The view port is the width and height of the window and the origin of
* the viewport (upper left corner) in world coordinates.
*/
int x, y;
int newX, newY;
int lineRequested; /* Line requested by "gotoline" command */
int first, last; /* Range of lines displayed */
/*
* Selections:
*/
Tk_3DBorder selBorder; /* Border and background for selected
* characters. */
int selBorderWidth; /* Width of border around selection. */
XColor *selFgColorPtr; /* Foreground color for selected text. */
GC selTextGC; /* For drawing selected text. */
/*
* Information about what's selected, if any.
*/
int selectFirst; /* Position of first selected character (-1
* means nothing selected). */
int selectLast; /* Position of last selected character (-1 means
* nothing selected). */
int selectAnchor; /* Fixed end of selection (i.e. "select to"
* operation will use this as one end of the
* selection). */
/*
* Information for scanning:
*/
int scanMarkX; /* X-position at which scan started (e.g. button
* was pressed here). */
int scanPtX; /* Position (x-offset) of the viewport when the
* scan started. */
int scanMarkY; /* Y-position at which scan started (e.g. button
* was pressed here). */
int scanPtY; /* Position (y-offset) of the viewport when the
* scan started. */
/* public: */
char *geometry;
char *yScrollCmd; /* Name of vertical scrollbar to invoke */
int yScrollUnits; /* # of pixels per vert scroll */
char *xScrollCmd; /* Name of horizontal scroll bar to invoke */
int xScrollUnits; /* # of pixels per horiz scroll */
int lineSpacing; /* # of pixels between lines */
int specChar; /* Special character designating a TCL command
* block in a hypertext file. */
XFontStruct *fontPtr; /* Font for normal text */
char *fileName; /* Name of hypertext file */
char *text; /* Text */
Cursor cursor; /* X Cursor for child */
} Hypertext;
#define DEF_HTEXT_ACTIVE_BG_COLOR BISQUE2
#define DEF_HTEXT_ACTIVE_BG_MONO BLACK
#define DEF_HTEXT_ACTIVE_FG_COLOR BLACK
#define DEF_HTEXT_ACTIVE_FG_MONO WHITE
#define DEF_HTEXT_BG_COLOR BISQUE1
#define DEF_HTEXT_BG_MONO WHITE
#define DEF_HTEXT_COMMAND ((char *) NULL)
#define DEF_HTEXT_CURSOR "pencil"
#define DEF_HTEXT_FONT "*-Helvetica-Bold-R-Normal-*-120-*"
#define DEF_HTEXT_FG BLACK
#define DEF_HTEXT_GEOMETRY "500x500"
#define DEF_HTEXT_OFF_VALUE "0"
#define DEF_HTEXT_ON_VALUE "1"
#define DEF_HTEXT_RELIEF "sunken"
#define DEF_HTEXT_SCROLL_UNITS "10"
#define DEF_HTEXT_LINE_SPACING "1"
#define DEF_HTEXT_SPEC_CHAR "0x25"
static Tk_ConfigSpec configSpecs[]=
{
{TK_CONFIG_BORDER, "-background", "background", "Background",
DEF_HTEXT_BG_COLOR, Tk_Offset (Hypertext, border), TK_CONFIG_COLOR_ONLY},
{TK_CONFIG_BORDER, "-background", "background", "Background",
DEF_HTEXT_BG_MONO, Tk_Offset (Hypertext, border), TK_CONFIG_MONO_ONLY},
{TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL,
(char *)NULL, 0, 0},
{TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL,
(char *)NULL, 0, 0},
{TK_CONFIG_FONT, "-font", "font", "Font",
DEF_HTEXT_FONT, Tk_Offset (Hypertext, fontPtr), 0},
{TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
DEF_HTEXT_FG, Tk_Offset (Hypertext, normalFg), 0},
{TK_CONFIG_STRING, "-geometry", "geometry", "Geometry",
DEF_HTEXT_GEOMETRY, Tk_Offset (Hypertext, geometry), 0},
{TK_CONFIG_STRING, "-text", "text", "Text",
(char *)NULL, Tk_Offset (Hypertext, text), 0},
{TK_CONFIG_STRING, "-filename", "fileName", "FileName",
(char *)NULL, Tk_Offset (Hypertext, fileName), 0},
{TK_CONFIG_INT, "-specialchar", "specialChar", "SpecialChar",
DEF_HTEXT_SPEC_CHAR, Tk_Offset (Hypertext, specChar), 0},
{TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
(char *)NULL, Tk_Offset (Hypertext, yScrollCmd), 0},
{TK_CONFIG_PIXELS, "-yscrollunits", "yScrollUnits", "yScrollUnits",
DEF_HTEXT_SCROLL_UNITS, Tk_Offset (Hypertext, yScrollUnits), 0},
{TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
(char *)NULL, Tk_Offset (Hypertext, xScrollCmd), 0},
{TK_CONFIG_PIXELS, "-xscrollunits", "xScrollUnits", "ScrollUnits",
DEF_HTEXT_SCROLL_UNITS, Tk_Offset (Hypertext, xScrollUnits), 0},
{TK_CONFIG_PIXELS, "-linespacing", "lineSpacing", "LineSpacing",
DEF_HTEXT_LINE_SPACING, Tk_Offset (Hypertext, lineSpacing), 0},
{TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
DEF_HTEXT_CURSOR, Tk_Offset (Hypertext, cursor), TK_CONFIG_NULL_OK},
{TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
(char *)NULL, 0, 0}
};
/* Forward Declarations */
static Hypertext *CreateText _ANSI_ARGS_ ((Tcl_Interp * interp,
Tk_Window tkwin, char *name));
static void DestroyText _ANSI_ARGS_ ((ClientData clientdata));
static int ConfigureText _ANSI_ARGS_ ((Tcl_Interp * interp,
Hypertext * textPtr, int argc, char **argv, int flags));
static void TextEventProc _ANSI_ARGS_ ((ClientData clientdata,
XEvent * eventPtr));
static void EventuallyRedraw _ANSI_ARGS_ ((Hypertext * textPtr));
static void TextScanTo _ANSI_ARGS_ ((Hypertext * textPtr, int x, int y));
static int ParseText _ANSI_ARGS_ ((Tcl_Interp * interp, Hypertext * textPtr));
static int ReadFile _ANSI_ARGS_ ((Tcl_Interp * interp, Hypertext * textPtr));
static int AppendChild _ANSI_ARGS_ ((Hypertext * textPtr, char *childName,
int argc, char **argv));
static Child *CreateChild _ANSI_ARGS_ ((Tcl_Interp * interp,
Hypertext * textPtr, char *childName));
static void DestroyChild _ANSI_ARGS_ ((Child * childPtr));
static void ChildStructureProc _ANSI_ARGS_ ((ClientData clientdata,
XEvent * eventPtr));
static int ConfigureChild _ANSI_ARGS_ ((Hypertext * textPtr, Child * childPtr,
int argc, char **argv, int flags));
static Child *FindChild _ANSI_ARGS_ ((Tcl_Interp * interp, Hypertext * textPtr,
char *childName));
static char *GetTclCommand _ANSI_ARGS_ ((Hypertext * textPtr, int *curPos,
char *command));
static Line *GetLine _ANSI_ARGS_ ((Hypertext * textPtr));
static void DestroyLine _ANSI_ARGS_ ((Line * linePtr));
static void SetLineText _ANSI_ARGS_ ((Line * linePtr, char *line, int size));
static void ComputeLayout _ANSI_ARGS_ ((Hypertext * textPtr, int *width,
int *height));
static void GetLineExtents _ANSI_ARGS_ ((Hypertext * textPtr, Line * linePtr));
static void FreeLines _ANSI_ARGS_ ((Hypertext * textPtr));
static void AdjustLinesAllocated _ANSI_ARGS_ ((Hypertext * textPtr));
static void DisplayText _ANSI_ARGS_ ((ClientData clientData));
static void DrawPage _ANSI_ARGS_ ((Hypertext * textPtr, int deltaY));
static void MoveChild _ANSI_ARGS_ ((Child * childPtr, int x, int y));
static void TextUpdateScrollBar _ANSI_ARGS_ ((Tcl_Interp * interp, char *cmd,
int total, int window, int first, int units));
static int GetVisibleLines _ANSI_ARGS_ ((Hypertext * textPtr));
static int LineSearch _ANSI_ARGS_ ((Hypertext * textPtr, int position,
int low, int high));
static char *reallocate _ANSI_ARGS_ ((char *object, unsigned int newSize,
unsigned int oldSize));
static void CreateTraces _ANSI_ARGS_ ((Hypertext * textPtr));
static void DeleteTraces _ANSI_ARGS_ ((Hypertext * textPtr));
static void ChildGeometryProc _ANSI_ARGS_ ((ClientData clientData,
Tk_Window tkwin));
static void SendExposeEvent _ANSI_ARGS_ ((Tk_Window tkwin));
extern void TkDisplayChars _ANSI_ARGS_ ((Display * display, Drawable drawable,
GC gc, XFontStruct * fontStructPtr, char *string, int numChars,
int x, int y, int flags));
extern int TkMeasureChars _ANSI_ARGS_ ((XFontStruct * fontStructPtr,
char *source, int maxChars, int startX, int maxX, int flags,
int *nextXPtr));
extern void TkBindError _ANSI_ARGS_ ((Tcl_Interp * interp));
/* end of Forward Declarations */
extern char *sys_errlist[];
static int
OptionChanged (offset, specs)
register int offset;
Tk_ConfigSpec specs[];
{
register Tk_ConfigSpec *specPtr;
for (specPtr = specs; specPtr->type != TK_CONFIG_END; specPtr++) {
if (offset == specPtr->offset)
return (specPtr->specFlags & TK_CONFIG_OPTION_SPECIFIED);
}
/* Can't be here */
fprintf (stderr, "Unknown option specified\n");
return (0);
}
/*
* --------------------------------------------------------------
*
* HypertextCmd --
*
* 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.
*
* --------------------------------------------------------------
*/
int
HypertextCmd (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. */
{
register Hypertext *textPtr;
Tk_Window tkwin = (Tk_Window) clientData;
if (argc < 2) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" pathName ?options?\"", NULL);
return TCL_ERROR;
}
textPtr = CreateText (interp, tkwin, argv[1]);
if (textPtr == NULL) {
return TCL_ERROR;
}
if (ConfigureText (interp, textPtr, argc - 2, argv + 2, 0) != TCL_OK) {
Tk_DestroyWindow (textPtr->tkwin);
return TCL_ERROR;
}
interp->result = Tk_PathName (textPtr->tkwin);
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 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. */
{
register Hypertext *textPtr = (Hypertext *) clientData;
Tk_Window tkwin = textPtr->tkwin;
int result = TCL_OK;
int length;
char c;
if (argc < 2) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" option ?arg arg ...?\"", NULL);
return TCL_ERROR;
}
Tk_Preserve ((ClientData) textPtr);
c = argv[1][0];
length = strlen (argv[1]);
if ((c == 'a') && (strncmp (argv[1], "append", length) == 0)) {
if (argc < 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" append pathName ?options?\"", NULL);
goto error;
}
if (AppendChild (textPtr, argv[2], argc - 3, argv + 3) != TCL_OK)
goto error;
goto redisplay;
} else if ((c == 'c') && (length > 1) &&
(strncmp (argv[1], "childconfigure", length) == 0)) {
Child *childPtr;
if (argc < 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" childconfigure childName ?args...?\"", NULL);
goto error;
}
childPtr = FindChild (interp, textPtr, argv[2]);
if (childPtr == NULL) {
Tcl_AppendResult (interp,
"Can't find any child window matching \"", argv[2], "\" in \"",
Tk_PathName (tkwin), "\"", NULL);
goto error;
}
if (argc == 3) {
return (Tk_ConfigureInfo (interp, tkwin, childConfigSpecs,
(char *)childPtr, (char *)NULL, 0));
} else if (argc == 4) {
return (Tk_ConfigureInfo (interp, tkwin, childConfigSpecs,
(char *)childPtr, argv[3], 0));
} else {
result = ConfigureChild (textPtr, childPtr, argc - 3, argv + 3,
TK_CONFIG_ARGV_ONLY);
}
goto redisplay;
} else if ((c == 'c') && (length > 1) &&
(strncmp (argv[1], "configure", length) == 0)) {
if (argc == 2) {
result = Tk_ConfigureInfo (interp, tkwin, configSpecs,
(char *)textPtr, (char *)NULL, 0);
} else if (argc == 3) {
result = Tk_ConfigureInfo (interp, tkwin, configSpecs,
(char *)textPtr, argv[2], 0);
} else {
result = ConfigureText (interp, textPtr, argc - 2, argv + 2,
TK_CONFIG_ARGV_ONLY);
goto redisplay;
}
} else if ((c == 'g') && (strncmp (argv[1], "gotoline", length) == 0)) {
char buf[80];
int lineNumber;
if (argc > 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" gotoline lineNumber\"", NULL);
goto error;
}
lineNumber = textPtr->first;
if (argc == 3) {
if (Tcl_GetInt (interp, argv[2], &lineNumber) != TCL_OK)
goto error;
if (lineNumber <= 0)
lineNumber = 0;
else if (lineNumber > textPtr->numLines)
lineNumber = textPtr->numLines - 1;
else
lineNumber--;
}
sprintf (buf, "%d", lineNumber + 1);
Tcl_SetResult (interp, buf, TCL_VOLATILE);
result = TCL_OK;
if ((textPtr->flags & GOTO_PENDING) || lineNumber != textPtr->first) {
/*
* Defer performing the actual scroll to later since the layout may
* may not be correct or the window may be unmapped.
*/
textPtr->lineRequested = lineNumber;
textPtr->flags |= (GOTO_PENDING | VIEWPORT_MOVED);
goto redisplay;
}
} else if ((c == 'x') && (strncmp (argv[1], "xview", length) == 0)) {
char buf[80];
int newX;
if (argc > 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" xview ?position?\"", NULL);
goto error;
}
newX = textPtr->x;
if (argc == 3) {
if (Tk_GetPixels (interp, tkwin, argv[2], &newX) != TCL_OK)
goto error;
newX *= textPtr->xScrollUnits; /* Convert to pixels */
if (newX > textPtr->width)
newX = textPtr->width - 1;
else if (newX < 0)
newX = 0;
}
sprintf (buf, "%d", newX / textPtr->xScrollUnits);
Tcl_SetResult (interp, buf, TCL_VOLATILE);
result = TCL_OK;
if (newX != textPtr->x) {
textPtr->newX = newX;
textPtr->flags |= VIEWPORT_MOVED;
goto redisplay;
}
} else if ((c == 'y') && (strncmp (argv[1], "yview", length) == 0)) {
char buf[80];
int newY;
if (argc > 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" yview ?position?\"", NULL);
goto error;
}
newY = textPtr->y;
if (argc == 3) {
if (Tk_GetPixels (interp, tkwin, argv[2], &newY) != TCL_OK)
goto error;
newY *= textPtr->yScrollUnits;
if (newY > textPtr->height)
newY = textPtr->height - 1;
else if (newY < 0)
newY = 0;
}
sprintf (buf, "%d", newY / textPtr->yScrollUnits);
Tcl_SetResult (interp, buf, TCL_VOLATILE);
result = TCL_OK;
if (newY != textPtr->y) {
textPtr->newY = newY;
textPtr->flags |= VIEWPORT_MOVED;
goto redisplay;
}
} else if ((c == 'm') && (strncmp (argv[1], "map", length) == 0)) {
Child *childPtr;
if (argc != 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" map childName\"", NULL);
goto error;
}
childPtr = FindChild (interp, textPtr, argv[2]);
if (childPtr == NULL) {
Tcl_AppendResult (interp,
"Can't find any child window matching \"", argv[2], "\" in \"",
Tk_PathName (tkwin), "\"", NULL);
goto error;
}
if (childPtr->tkwin != NULL) {
Tk_MapWindow (childPtr->tkwin);
childPtr->flags |= MAPPED;
}
} else if ((c == 'u') && (strncmp (argv[1], "unmap", length) == 0)) {
Child *childPtr;
if (argc != 3) {
Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
" unmap childName\"", NULL);
goto error;
}
childPtr = FindChild (interp, textPtr, argv[2]);
if (childPtr == NULL) {
Tcl_AppendResult (interp,
"Can't find any child window matching \"", argv[2], "\" in \"",
Tk_PathName (tkwin), "\"", NULL);
goto error;
}
if (childPtr->tkwin != NULL) {
Tk_UnmapWindow (childPtr->tkwin);
childPtr->flags &= ~MAPPED;
}
} else if ((c == 's') && (length > 1)
&& (strncmp (argv[1], "scan", length) == 0)) {
int x, y;
if (argc != 5) {
Tcl_AppendResult (interp, "wrong # args: should be \"",
argv[0], " scan mark|dragto x y\"", (char *)NULL);
goto error;
}
if (Tcl_GetInt (interp, argv[3], &x) != TCL_OK ||
Tcl_GetInt (interp, argv[4], &y) != TCL_OK) {
goto error;
}
c = argv[2][0];
length = strlen (argv[2]);
if ((c == 'm') && (strncmp (argv[2], "mark", length) == 0)) {
textPtr->scanMarkX = x;
textPtr->scanMarkY = y;
textPtr->scanPtX = textPtr->x;
textPtr->scanPtY = textPtr->y;
} else if ((c == 'd') && (strncmp (argv[2], "dragto", length) == 0)) {
TextScanTo (textPtr, x, y);
} else {
Tcl_AppendResult (interp, "bad scan option \"", argv[2],
"\": must be mark or dragto", (char *)NULL);
goto error;
}
#ifdef notdef
} else if ((c == 's') && (length > 1) &&
strncmp (argv[1], "select", length) == 0) {
int index;
if (argc < 3) {
Tcl_AppendResult (interp, "too few args: should be \"",
argv[0], " select option ?index?\"", (char *)NULL);
goto error;
}
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], " select clear\"", (char *)NULL);
goto error;
}
if (textPtr->selectFirst != -1) {
textPtr->selectFirst = textPtr->selectLast = -1;
goto redisplay;
}
goto error;
}
if (argc >= 4) {
if (GetTextIndex (interp, textPtr, argv[3], &index) != TCL_OK) {
goto error;
}
}
if ((c == 'a') && (strncmp (argv[2], "adjust", length) == 0)) {
if (argc != 4) {
Tcl_AppendResult (interp, "wrong # args: should be \"",
argv[0], " select adjust index\"",
(char *)NULL);
goto error;
}
TextSelectTo (textPtr, index);
} else if ((c == 'f') && (strncmp (argv[2], "from", length) == 0)) {
if (argc != 4) {
Tcl_AppendResult (interp, "wrong # args: should be \"",
argv[0], " select from index\"",
(char *)NULL);
goto error;
}
textPtr->selectAnchor = index;
} else if ((c == 't') && (strncmp (argv[2], "to", length) == 0)) {
if (argc != 4) {
Tcl_AppendResult (interp, "wrong # args: should be \"",
argv[0], " select to index\"",
(char *)NULL);
goto error;
}
TextSelectTo (textPtr, index);
} else {
Tcl_AppendResult (interp, "bad select option \"", argv[2],
"\": must be adjust, clear, from, or to", (char *)NULL);
goto error;
}
#endif
} else {
Tcl_AppendResult (interp, "bad option \"", argv[1], "\":",
" should be append, configure, childconfigure, ",
"gotoline, map, unmap, xview, or yview", NULL);
goto error;
}
Tk_Release ((ClientData) textPtr);
return result;
redisplay:
EventuallyRedraw (textPtr);
Tk_Release ((ClientData) textPtr);
return TCL_OK;
error:
Tk_Release ((ClientData) textPtr);
return TCL_ERROR;
}
/*
* ----------------------------------------------------------------------
*
* CreateText --
*
* This procedure creates and initializes a new hypertext widget.
*
* Results:
* The return value is a pointer to a structure describing the new
* 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, a Tk_Window is created, etc.
*
* ----------------------------------------------------------------------
*/
static Hypertext *
CreateText (interp, tkwin, pathName)
Tcl_Interp *interp; /* Used for error reporting. */
Tk_Window tkwin; /* Window to use for resolving pathName. */
char *pathName; /* Name for new window. */
{
register Hypertext *textPtr;
Tk_Window new;
/*
* Create the new window.
*/
new = Tk_CreateWindowFromPath (interp, tkwin, pathName, (char *)NULL);
if (new == NULL) {
return (NULL);
}
Tk_SetClass (new, "Hypertext");
/*
* Initialize the data structure for the Hypertext.
*/
textPtr = (Hypertext *) calloc (1, sizeof (Hypertext));
if (textPtr == NULL) {
return (NULL);
}
textPtr->tkwin = new;
textPtr->interp = interp;
textPtr->numLines = textPtr->arraySize = 0;
Tk_CreateEventHandler (new, ExposureMask | StructureNotifyMask,
TextEventProc, (ClientData) textPtr);
Tcl_CreateCommand (interp, pathName, TextWidgetCmd, (ClientData) textPtr,
(Tcl_CmdDeleteProc *) NULL);
return textPtr;
}
/*
* ----------------------------------------------------------------------
*
* DestroyText --
*
* This procedure is invoked by Tk_EventuallyFree or Tk_Release
* to clean up the internal structure of a Hypertext 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 (clientData)
ClientData clientData; /* Info about hypertext widget. */
{
register Hypertext *textPtr = (Hypertext *) clientData;
/* Free allocated memory for the following: */
if (textPtr->gc != None) /* Graphics context */
Tk_FreeGC (textPtr->gc);
if (textPtr->border) /* 3D Border */
Tk_Free3DBorder (textPtr->border);
if (textPtr->normalFg) /* Foreground color */
Tk_FreeColor (textPtr->normalFg);
if (textPtr->geometry) /* Geometry */
ckfree (textPtr->geometry);
if (textPtr->yScrollCmd) /* Y scroll command */
ckfree (textPtr->yScrollCmd);
if (textPtr->xScrollCmd) /* X scroll command */
ckfree (textPtr->xScrollCmd);
if (textPtr->fontPtr) /* Font */
Tk_FreeFontStruct (textPtr->fontPtr);
if (textPtr->fileName) /* Filename */
ckfree (textPtr->fileName);
if (textPtr->text != NULL) /* Text string */
ckfree (textPtr->text);
if (textPtr->cursor != None) /* Cursor */
Tk_FreeCursor (textPtr->cursor);
FreeLines (textPtr);
ckfree ((char *)textPtr);/* */
}
/*
* ----------------------------------------------------------------------
*
* 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, -filename, -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 textPtr; old resources get freed, if there were any.
* The hypertext is redisplayed.
*
* ----------------------------------------------------------------------
*/
static int
ConfigureText (interp, textPtr, argc, argv, flags)
Tcl_Interp *interp; /* Used for error reporting. */
Hypertext *textPtr; /* Information about widget; may or may not
* already have values for some fields. */
int argc; /* Number of valid entries in argv. */
char **argv; /* Arguments. */
int flags; /* Flags to pass to Tk_ConfigureWidget. */
{
XGCValues gcValues;
unsigned long valueMask;
GC newGC;
Tk_Window tkwin = textPtr->tkwin;
if (Tk_ConfigureWidget (interp, tkwin, configSpecs, argc, argv,
(char *)textPtr, flags) != TCL_OK) {
return TCL_ERROR;
}
Tk_SetBackgroundFromBorder (tkwin, textPtr->border);
if (OptionChanged (Tk_Offset (Hypertext, fontPtr), configSpecs) ||
OptionChanged (Tk_Offset (Hypertext, lineSpacing), configSpecs)) {
textPtr->flags |= LAYOUT_NEEDED;
}
gcValues.font = textPtr->fontPtr->fid;
gcValues.foreground = textPtr->normalFg->pixel;
valueMask = GCForeground | GCFont;
newGC = Tk_GetGC (tkwin, valueMask, &gcValues);
if (textPtr->gc != None)
Tk_FreeGC (textPtr->gc);
textPtr->gc = newGC;
/* Kill the -file option, if the -text option exists */
if (textPtr->text != NULL) {
if (textPtr->fileName != NULL)
ckfree (textPtr->fileName);
textPtr->fileName = NULL;
} else if (OptionChanged (Tk_Offset (Hypertext, fileName), configSpecs)) {
if (ReadFile (interp, textPtr) != TCL_OK)
return (TCL_ERROR);
}
/* If new text is available, read it into the widget */
if (textPtr->text != NULL) {
if (ParseText (interp, textPtr) != TCL_OK)
return (TCL_ERROR);
textPtr->flags |= LAYOUT_NEEDED; /* Mark for layout update */
}
if (textPtr->geometry != NULL) {
int height, width;
if (sscanf (textPtr->geometry, "%dx%d", &width, &height) != 2) {
Tcl_AppendResult (interp, "bad geometry \"", textPtr->geometry,
"\": expected widthxheight", (char *)NULL);
return (TCL_ERROR);
}
Tk_GeometryRequest (tkwin, width, height);
}
/* Lastly, arrange for the hypertext to be redisplayed. */
EventuallyRedraw (textPtr);
return TCL_OK;
}
/*
* --------------------------------------------------------------
*
* 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. */
{
Hypertext *textPtr = (Hypertext *) clientData;
switch (eventPtr->type) {
case ConfigureNotify:
textPtr->flags |= VIEWPORT_RESIZED;
EventuallyRedraw (textPtr);
break;
case Expose:
if (eventPtr->xexpose.send_event) {
textPtr->flags ^= IGNORE_EXPOSURES;
return;
}
if ((eventPtr->xexpose.count == 0) &&
!(textPtr->flags & IGNORE_EXPOSURES)) {
EventuallyRedraw (textPtr);
}
break;
case DestroyNotify:
Tcl_DeleteCommand (textPtr->interp, Tk_PathName (textPtr->tkwin));
textPtr->tkwin = NULL;
if (textPtr->flags & REDRAW_PENDING)
Tk_CancelIdleCall (DisplayText, (ClientData) textPtr);
Tk_EventuallyFree ((ClientData) textPtr, DestroyText);
break;
}
}
/*
*----------------------------------------------------------------------
*
* EventuallyRedraw --
*
* Ensure that an entry is eventually redrawn on the display.
*
* 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 (textPtr)
register Hypertext *textPtr; /* Information about widget. */
{
if ((textPtr->tkwin != NULL) && (Tk_IsMapped (textPtr->tkwin)) &&
!(textPtr->flags & REDRAW_PENDING)) {
textPtr->flags |= REDRAW_PENDING;
Tk_DoWhenIdle (DisplayText, (ClientData) textPtr);
}
}
/*
*----------------------------------------------------------------------
*
* TextScanTo --
*
* Given a XY coordinates (presumably of the curent mouse location)
* drag the view in the window to implement the scan operation.
*
* Results:
* None.
*
* Side effects:
* The view in the window may change and the window redrawn.
*
*----------------------------------------------------------------------
*/
static void
TextScanTo (textPtr, x, y)
register Hypertext *textPtr; /* Information about widget. */
int x, y; /* Coordinate to use for scan operation. */
{
int newX, newY;
/* Amplify X distance from point to mark by step of horizontal units */
newX = textPtr->scanPtX - (x - textPtr->scanMarkX) * textPtr->xScrollUnits;
/* Amplify Y distance from point to mark by 10x vertical units */
newY = (textPtr->scanPtY - (10 * (y - textPtr->scanMarkY)) *
textPtr->yScrollUnits);
if (newX < 0) {
newX = textPtr->scanPtX = 0;
textPtr->scanMarkX = x;
} else if (newX >= textPtr->width) {
newX = textPtr->scanPtX = textPtr->width - textPtr->xScrollUnits;
textPtr->scanMarkX = x;
}
if (newY < 0) {
newY = textPtr->scanPtY = 0;
textPtr->scanMarkY = y;
} else if (newY >= textPtr->height) {
newY = textPtr->scanPtY = textPtr->height - textPtr->yScrollUnits;
textPtr->scanMarkY = y;
}
if (newY != textPtr->newY || newX != textPtr->newX) {
textPtr->newX = newX, textPtr->newY = newY;
textPtr->flags |= VIEWPORT_MOVED;
EventuallyRedraw (textPtr);
}
}
/*
* ----------------------------------------------------------------------
*
* GetLine --
*
* 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 *
GetLine (textPtr)
Hypertext *textPtr;
{
Line *linePtr;
if (textPtr->numLines >= textPtr->arraySize) {
Line **newPtr;
/* reallocate the array of lines */
if (textPtr->arraySize == 0) {
textPtr->arraySize = LINES_ALLOC_CHUNK;
} else {
/* Double the size of the array */
textPtr->arraySize += textPtr->arraySize;
}
newPtr = (Line **) reallocate ((char *)textPtr->lineArr,
textPtr->arraySize * sizeof (Line *),
textPtr->numLines * sizeof (Line *));
textPtr->lineArr = newPtr;
}
/* Create new line entry and add to table */
linePtr = (Line *) calloc (1, sizeof (Line));
if (linePtr == NULL) {
Tcl_AppendResult (textPtr->interp, "calloc: ", sys_errlist[errno],
": Can't allocate new line ", NULL);
return (NULL);
}
textPtr->lineArr[textPtr->numLines++] = linePtr;
return (linePtr);
}
/*
* ----------------------------------------------------------------------
*
* DestroyLine --
*
* This procedure is invoked by FreeLines to clean up the
* internal structure of a line.
*
* Results: None.
*
* Side effects:
* Everything associated with the line (text and children) is
* freed up.
*
* ----------------------------------------------------------------------
*/
static void
DestroyLine (linePtr)
register Line *linePtr;
{
register Child *childPtr = linePtr->children;
register Child *oldPtr;
/* Free the list of child structures */
while (childPtr != NULL) {
oldPtr = childPtr;
childPtr = childPtr->nextPtr;
DestroyChild (oldPtr);
}
/* Deallocate the text array */
if (linePtr->text != NULL)
ckfree (linePtr->text);
ckfree ((char *)linePtr);
}
/*
* ----------------------------------------------------------------------
*
* AppendChild --
*
* This procedure creates and initializes a new hyper text child.
*
* Results:
* The return value is a standard Tcl result.
*
* Side effects:
* Memory is allocated. Child gets configured.
*
* ----------------------------------------------------------------------
*/
static int
AppendChild (textPtr, childName, argc, argv)
register Hypertext *textPtr;
char *childName;
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
Line *linePtr;
Child *childPtr;
childPtr = CreateChild (textPtr->interp, textPtr, childName);
if (childPtr == NULL)
return TCL_ERROR;
if (ConfigureChild (textPtr, childPtr, argc, argv, 0) != TCL_OK)
return (TCL_ERROR);
/* Append child to list of subwindows of the last line */
/* Check that there is a line to append the window */
if (textPtr->numLines == 0)
GetLine (textPtr);
linePtr = textPtr->lineArr[textPtr->numLines - 1];
if (linePtr->children == NULL) {
linePtr->lastChild = linePtr->children = childPtr;
} else {
linePtr->lastChild->nextPtr = childPtr;
linePtr->lastChild = childPtr;
}
linePtr->width += childPtr->width;
childPtr->precedingTextEnd = linePtr->textLength;
textPtr->flags |= LAYOUT_NEEDED;
return (TCL_OK);
}
/*
* ----------------------------------------------------------------------
*
* CreateChild --
*
* This procedure creates and initializes a new child subwindow
* in the hyper text widget.
*
* Results:
* The return value is a pointer to a structure describing the
* new child. If an error occurred, then the return value is
* NULL and an error message is left in interp->result.
*
* Side effects:
* Memory is allocated. Child window is mapped. Callbacks are set
* up for subwindow resizes and geometry requests.
*
* ----------------------------------------------------------------------
*/
static Child *
CreateChild (interp, textPtr, childName)
Tcl_Interp *interp; /* Used for error reporting. */
Hypertext *textPtr; /* Hypertext widget */
char *childName; /* Name of child window */
{
register Child *childPtr;
Tk_Window tkwin;
char buf[BUFSIZ];
if (*childName != '.') { /* Relative path, make absolute */
sprintf (buf, "%s.%s", Tk_PathName (textPtr->tkwin), childName);
childName = buf;
}
/* Get the Tk window and parent Tk window associated with the child */
tkwin = Tk_NameToWindow (interp, childName, textPtr->tkwin);
if (tkwin == NULL) {
Tcl_AppendResult (interp, "Can't find a window \"", childName, NULL);
return (NULL);
}
if (FindChild (interp, textPtr, childName) != NULL) {
Tcl_AppendResult (interp, "\"", childName,
"\" is already appended to ",
Tk_PathName (textPtr->tkwin));
return (NULL);
}
if (textPtr->tkwin != Tk_Parent (tkwin)) {
Tcl_AppendResult (interp, "\"", childName, "\" is not a child of `%s'",
Tk_PathName (textPtr->tkwin));
return (NULL);
}
childPtr = (Child *) calloc (1, sizeof (Child));
if (childPtr == NULL) {
Tcl_AppendResult (interp, "calloc: ", sys_errlist[errno],
": Can't create child structure", NULL);
return (NULL);
}
childPtr->tkwin = tkwin;
childPtr->parent = textPtr;
/* Map the window so that we can query its width and height */
Tk_MapWindow (tkwin);
childPtr->width = Tk_ReqWidth (tkwin);
childPtr->height = Tk_ReqHeight (tkwin);
/* Set up callbacks for geometry requests and window structure changes */
Tk_ManageGeometry (tkwin, ChildGeometryProc, (ClientData) childPtr);
Tk_CreateEventHandler (tkwin, StructureNotifyMask, ChildStructureProc,
(ClientData) childPtr);
return (childPtr);
}
/*
* ----------------------------------------------------------------------
*
* DestroyChild --
*
* This procedure is invoked by DestroyLine to clean up the
* internal structure of a child.
*
* Results:
* None.
*
* Side effects:
* Everything associated with the widget is freed up.
*
* ----------------------------------------------------------------------
*/
static void
DestroyChild (childPtr)
register Child *childPtr;
{
/* Destroy the child window if it still exists */
if (childPtr->tkwin != NULL)
Tk_DestroyWindow (childPtr->tkwin);
free ((char *)childPtr);
}
/*
* ----------------------------------------------------------------------
*
* ConfigureChild --
*
* This procedure is called to process an argv/argc list, plus
* the Tk option database, in order to configure (or reconfigure)
* a hypertext child.
*
* 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 the child; old resources get freed, if there
* were any. The child marked for redisplay.
*
* ----------------------------------------------------------------------
*/
static int
ConfigureChild (textPtr, childPtr, argc, argv, flags)
Hypertext *textPtr; /* Parent hypertext widget. */
Child *childPtr; /* Information about child; may or may not
* already have values for some fields. */
int argc; /* Number of valid entries in argv. */
char **argv; /* Arguments. */
int flags;
{
int oldAnchor;
int oldPadX, oldPadY;
oldAnchor = childPtr->anchor;
oldPadX = childPtr->padX;
oldPadY = childPtr->padY;
if (Tk_ConfigureWidget (textPtr->interp, textPtr->tkwin, childConfigSpecs,
argc, argv, (char *)childPtr, flags) != TCL_OK)
return (TCL_ERROR);
/* If non-zero use the user-defined space constraints. */
childPtr->width = (childPtr->widthWanted > 0)
? childPtr->widthWanted : Tk_ReqWidth (childPtr->tkwin);
childPtr->height = (childPtr->heightWanted > 0)
? childPtr->heightWanted : Tk_ReqHeight (childPtr->tkwin);
/*
* If the requested new width or height of the child is different from
* the current, we need to recompute the layout.
*/
if (childPtr->width != Tk_Width (childPtr->tkwin) ||
childPtr->height != Tk_Height (childPtr->tkwin) ||
childPtr->padX != oldPadX || childPtr->padY != oldPadY ||
childPtr->anchor != oldAnchor)
textPtr->flags |= LAYOUT_NEEDED;
return (TCL_OK);
}
/*
* --------------------------------------------------------------
*
* 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
ChildStructureProc (clientData, eventPtr)
ClientData clientData; /* Information about window. */
XEvent *eventPtr; /* Information about event. */
{
register Child *childPtr = (Child *) clientData;
if (childPtr != NULL && childPtr->tkwin != NULL) {
Hypertext *textPtr;
textPtr = childPtr->parent;
switch (eventPtr->type) {
case DestroyNotify:
/*
* Mark the child as deleted by dereferencing the Tk window
* pointer. Zero out the height and width to collapse the area
* used by the child. Redraw the screen only if the child is
* currently visible and mapped.
*/
childPtr->tkwin = NULL;
childPtr->width = childPtr->height = 0;
childPtr->parent->flags |= LAYOUT_NEEDED;
if ((childPtr->flags & (VISIBLE | MAPPED)) == (VISIBLE | MAPPED)) {
EventuallyRedraw (textPtr);
}
break;
case ConfigureNotify:
/*
* Children can't request new XY positions by themselves, so
* worry only about resizing.
*/
if (childPtr->width != Tk_Width (childPtr->tkwin) ||
childPtr->height != Tk_Height (childPtr->tkwin)) {
EventuallyRedraw (textPtr);
textPtr->flags |= LAYOUT_NEEDED;
}
break;
}
}
}
/*
*----------------------------------------------------------------------
*
* ComputeLayout --
*
* This procedure computes the total width and height needed
* to contain the text and children 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 (textPtr, layoutWidth, layoutHeight)
Hypertext *textPtr;
int *layoutWidth;
int *layoutHeight;
{
register int cnt;
register Line *linePtr;
register int height, width;
width = height = 0;
for (cnt = 0; cnt < textPtr->numLines; cnt++) {
linePtr = textPtr->lineArr[cnt];
linePtr->offset = height;
GetLineExtents (textPtr, linePtr);
height += linePtr->height;
if (linePtr->width > width)
width = linePtr->width;
}
/* Save new height and width */
*layoutHeight = height;
*layoutWidth = width;
textPtr->flags &= ~LAYOUT_NEEDED;
/* Indicate if new layout changed size of world */
if (height != textPtr->height || width != textPtr->width)
textPtr->flags |= LAYOUT_CHANGED;
}
/*
*----------------------------------------------------------------------
*
* GetLineExtents --
*
* This procedure computes the total width and height needed
* to contain the text and children for a particular line.
* It also calculates the baseline of the text on the line with
* respect to the other children on the line.
*
* Results:
* None.
*
*----------------------------------------------------------------------
*/
static void
GetLineExtents (textPtr, linePtr)
Hypertext *textPtr;
Line *linePtr;
{
register Child *childPtr;
register int width;
register int baseline;
int textLength;
int maxAscent, maxDescent, maxHeight;
int ascent, descent, height;
register int curPos = 0;
int median; /* Difference of font ascent/descent values */
/*
* Pass 1: Determine the maximum ascent (baseline) and descent needed for
* the line. We'll need this for figuring the top/bottom/center anchors.
*/
/* Initialize line defaults */
maxAscent = textPtr->fontPtr->ascent;
maxDescent = textPtr->fontPtr->descent;
baseline = textPtr->fontPtr->ascent;
median = textPtr->fontPtr->ascent - textPtr->fontPtr->descent;
for (childPtr = linePtr->children;
childPtr != NULL; childPtr = childPtr->nextPtr) {
height = childPtr->height + 2 * childPtr->padY;
switch (childPtr->anchor) {
case TK_ANCHOR_N:
case TK_ANCHOR_NE:
case TK_ANCHOR_NW:
ascent = textPtr->fontPtr->ascent + childPtr->padY;
descent = height - textPtr->fontPtr->ascent;
break;
case TK_ANCHOR_E:
case TK_ANCHOR_W:
case TK_ANCHOR_CENTER:
ascent = (height + median) / 2;
descent = (height - median) / 2;
break;
case TK_ANCHOR_S:
case TK_ANCHOR_SE:
case TK_ANCHOR_SW:
ascent = height - textPtr->fontPtr->descent;
descent = textPtr->fontPtr->descent;
break;
}
if (descent > maxDescent)
maxDescent = descent;
if (ascent > maxAscent)
maxAscent = ascent;
}
baseline = maxAscent + linePtr->offset;
maxHeight = maxAscent + maxDescent + textPtr->lineSpacing;
width = 0; /* Always starts from x=0 */
/*
* Pass 2: Find the placements of the text and children along each line.
*/
for (childPtr = linePtr->children; childPtr != NULL;
childPtr = childPtr->nextPtr) {
/* Get the width of the text leading to the child */
textLength = (childPtr->precedingTextEnd - curPos);
if (textLength > 0) {
int newWidth = 0;
/* Text extents of normal text */
TkMeasureChars (textPtr->fontPtr, linePtr->text + curPos,
textLength, width, 10000,
TK_PARTIAL_OK | TK_AT_LEAST_ONE, &newWidth);
childPtr->precedingTextWidth = newWidth - width;
width = newWidth;
}
width += childPtr->padX;
/* Save the world XY coordinates of the child */
childPtr->x = width;
switch (childPtr->anchor) {
case TK_ANCHOR_N:
case TK_ANCHOR_NE:
case TK_ANCHOR_NW:
childPtr->y = baseline - textPtr->fontPtr->ascent;
break;
case TK_ANCHOR_E:
case TK_ANCHOR_W:
case TK_ANCHOR_CENTER:
childPtr->y = baseline - (childPtr->height + median) / 2;
break;
case TK_ANCHOR_S:
case TK_ANCHOR_SE:
case TK_ANCHOR_SW:
childPtr->y = (baseline -
(childPtr->height - textPtr->fontPtr->descent));
break;
}
width += childPtr->width + childPtr->padX;
curPos = childPtr->precedingTextEnd;
}
/*
* This may be piece of line after last child and will also pick up the
* entire line if no children occured on it
*/
textLength = (linePtr->textLength - curPos);
if (textLength > 0) {
int newWidth = 0;
/* Text extents of normal text */
TkMeasureChars (textPtr->fontPtr, linePtr->text + curPos, textLength,
width, 10000, TK_PARTIAL_OK | TK_AT_LEAST_ONE,
&newWidth);
width = newWidth;
}
/* Update line parameters */
linePtr->width = width;
linePtr->height = maxHeight;
linePtr->baseline = maxAscent;
}
/*
* ----------------------------------------------------------------------
*
* 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 occuring between
* the parent and child windows.
*
* Recompute the layout of the text if necessary. This is
* necessary if the world coordinate system has changed.
* Specifically, the following may have occurred:
*
* - a text attribute has changed (font, linespacing, etc.).
* - child option changed (anchor, width, height).
* - actual child window was resized.
* - new text string or file.
*
* This is defered to the display routine since potentially
* many of these may occur (especially child window 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 child windows
* must be moved off the viewport. Since child windows will
* obscure the text window, it is imperative that the children
* are moved off before we try to redraw text in the same area.
* This is necessary only for vertical movements. Horizontal
* child window 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
* (child window 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
* child windows off the viewport.
*
* Potentially many expose events may be generated when the
* the individual child windows are moved and/or resized.
* These events need to be ignored. Since (I think) expose
* events are guarenteed to happen in order, we can bracket
* them by sending phony events (via XSendEvent). The phony
* event turn on and off flags which indicate if the 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. */
{
Hypertext *textPtr = (Hypertext *) clientData;
register Tk_Window tkwin;
int oldFirst; /* First line of old viewport */
int oldLast; /* Last line of old viewport */
int deltaY; /* Change in viewport in Y direction */
textPtr->flags &= ~REDRAW_PENDING;
tkwin = textPtr->tkwin;
if ((tkwin == NULL) || !Tk_IsMapped (tkwin) || (textPtr->numLines <= 0)) {
return;
}
/*
* Recalculate the layout. Do this when child positions or sizes have
* changed, or the text attributes (font, linespacing, etc) have changed.
* And when a initially using new file or text string, the child
* positions can't be trusted.
*/
if (textPtr->flags & LAYOUT_NEEDED) {
int width, height;
ComputeLayout (textPtr, &width, &height);
textPtr->width = width, textPtr->height = height;
}
/* Is there a pending gotoline request? */
if (textPtr->flags & GOTO_PENDING) {
textPtr->newY = textPtr->lineArr[textPtr->lineRequested]->offset;
textPtr->flags &= ~GOTO_PENDING;
}
deltaY = textPtr->newY - textPtr->y;
oldFirst = textPtr->first, oldLast = textPtr->last;
/*
* If the viewport has changed size or position, or the text and/or child
* subwindows have changed, adjust the scrollbars to new positions.
*/
if (textPtr->flags & (VIEWPORT_MOVED | VIEWPORT_RESIZED | LAYOUT_CHANGED)) {
/* Reset viewport origin and world extents */
textPtr->x = textPtr->newX, textPtr->y = textPtr->newY;
/* Horizontal scrollbar */
if (!NULLSTR (textPtr->xScrollCmd))
TextUpdateScrollBar (textPtr->interp, textPtr->xScrollCmd,
textPtr->width, Tk_Width (tkwin), textPtr->x,
textPtr->xScrollUnits);
/* Vertical scrollbar */
if (!NULLSTR (textPtr->yScrollCmd))
TextUpdateScrollBar (textPtr->interp, textPtr->yScrollCmd,
textPtr->height, Tk_Height (tkwin),
textPtr->y, textPtr->yScrollUnits);
/*
* Given a new viewport or text height, find the first and last line
* numbers of the new viewport.
*/
GetVisibleLines (textPtr);
}
/*
* (This is a kludge.) Send an expose event before and after drawing the
* page of text. Since moving and resizing of the subwindows will cause
* redundant expose events in the parent window, the phony events will
* bracket them indicating no action should be taken.
*/
SendExposeEvent (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 children
* from the previous viewport out of the current viewport. Worry only
* about the vertical child window movements. The horizontal moves are
* handled by the when drawing the page of text.
*/
if (textPtr->first != oldFirst || textPtr->last != oldLast) {
register int cnt;
int first, last;
register Child *childPtr;
/* Figure out which lines are now *out* of the viewport */
if (textPtr->first > oldFirst && textPtr->first <= oldLast)
first = oldFirst, last = textPtr->first;
else if (textPtr->last < oldLast && textPtr->last >= oldFirst)
first = textPtr->last, last = oldLast;
else
first = oldFirst, last = oldLast;
for (cnt = first; cnt <= last; cnt++) {
for (childPtr = textPtr->lineArr[cnt]->children;
childPtr != NULL; childPtr = childPtr->nextPtr) {
MoveChild (childPtr, textPtr->x, textPtr->y);
childPtr->flags &= ~VISIBLE;
}
}
}
DrawPage (textPtr, deltaY);
SendExposeEvent (tkwin);
/* Reset flags */
textPtr->flags &= ~(VIEWPORT_RESIZED | VIEWPORT_MOVED | LAYOUT_CHANGED);
}
/*
* ----------------------------------------------------------------------
*
* DrawPage --
*
* This procedure displays the lines of text and moves the child
* windows 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 window appear to bump into
* the next line before that line is moved. At worst case, where
* every line has a bottom 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 substract 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 (textPtr, deltaY)
Hypertext *textPtr;
int deltaY; /* Change from previous Y coordinate */
{
Line *linePtr;
Child *childPtr;
Tk_Window tkwin = textPtr->tkwin;
int textLength;
int curPos;
int baseline;
Pixmap pixMap;
int forceCopy = FALSE;
register int cnt;
int curLine, lastY;
register int x, y;
/* Setup: Clear the display */
/* Create an off-screen pixmap for semi-smooth scrolling. */
pixMap = XCreatePixmap (Tk_Display (tkwin), Tk_WindowId (tkwin),
Tk_Width (tkwin), Tk_Height (tkwin),
DefaultDepthOfScreen (Tk_Screen (tkwin)));
Tk_Fill3DRectangle (Tk_Display (tkwin), pixMap, textPtr->border,
0, 0, Tk_Width (tkwin), Tk_Height (tkwin),
0, TK_RELIEF_FLAT);
x = -(textPtr->x);
y = -(textPtr->y);
if (deltaY >= 0) {
y += textPtr->lineArr[textPtr->first]->offset;
curLine = textPtr->first;
lastY = 0;
} else {
y += textPtr->lineArr[textPtr->last]->offset;
curLine = textPtr->last;
lastY = Tk_Height (tkwin);
}
forceCopy = FALSE;
/* Draw each line */
for (cnt = textPtr->first; cnt <= textPtr->last; cnt++) {
/* Initialize character position in text buffer to start */
curPos = 0;
/* Initialize X position */
x = -(textPtr->x);
linePtr = textPtr->lineArr[curLine];
baseline = y + linePtr->baseline; /* Base line in screen
* coordinates */
for (childPtr = linePtr->children; childPtr != NULL;
childPtr = childPtr->nextPtr) {
MoveChild (childPtr, textPtr->x, textPtr->y);
childPtr->flags |= VISIBLE;
textLength = (childPtr->precedingTextEnd - curPos);
if (textLength > 0) {
TkDisplayChars (Tk_Display (tkwin), pixMap, textPtr->gc,
textPtr->fontPtr, linePtr->text + curPos,
textLength, x, baseline, 0);
x += childPtr->precedingTextWidth;
}
curPos = childPtr->precedingTextEnd;
x += childPtr->width + 2 * childPtr->padX;
forceCopy++;
}
/*
* This may be the text trailing the last child or the entire line if
* no children occur on it.
*/
textLength = (linePtr->textLength - curPos);
if (textLength > 0) {
TkDisplayChars (Tk_Display (tkwin), pixMap, textPtr->gc,
textPtr->fontPtr, linePtr->text + curPos,
textLength, x, baseline, 0);
}
/* Go to the top of the next line */
if (deltaY >= 0) {
y += textPtr->lineArr[curLine++]->height;
}
if (forceCopy > 0 && !(textPtr->flags & VIEWPORT_RESIZED)) {
if (deltaY >= 0) {
XCopyArea (Tk_Display (tkwin), pixMap, Tk_WindowId (tkwin),
textPtr->gc, 0, lastY, Tk_Width (tkwin), y - lastY,
0, lastY);
} else {
XCopyArea (Tk_Display (tkwin), pixMap, Tk_WindowId (tkwin),
textPtr->gc, 0, y, Tk_Width (tkwin), lastY - y,
0, y);
}
forceCopy = 0; /* Reset drawing flag */
lastY = y; /* Record last Y position */
}
if ((deltaY < 0) && (curLine > 0)) {
y -= textPtr->lineArr[--curLine]->height;
}
}
/* Prologue */
if (textPtr->flags & VIEWPORT_RESIZED) {
XCopyArea (Tk_Display (tkwin), pixMap, Tk_WindowId (tkwin),
textPtr->gc, 0, 0, Tk_Width (tkwin), Tk_Height (tkwin), 0, 0);
} else if (lastY != y) {
if (deltaY >= 0) {
XCopyArea (Tk_Display (tkwin), pixMap, Tk_WindowId (tkwin),
textPtr->gc, 0, lastY, Tk_Width (tkwin),
Tk_Height (tkwin) - lastY, 0, lastY);
} else {
XCopyArea (Tk_Display (tkwin), pixMap, Tk_WindowId (tkwin),
textPtr->gc, 0, 0, Tk_Width (tkwin), lastY, 0, 0);
}
}
XFreePixmap (Tk_Display (tkwin), pixMap);
}
/*
* ----------------------------------------------------------------------
*
* MoveChild --
*
* Move a child subwindow 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 subwindow is moved to its new location, generating
* Expose events in the parent for each child window moved.
*
* ----------------------------------------------------------------------
*/
static void
MoveChild (childPtr, newX, newY)
register Child *childPtr;
int newX; /* X-coordinate from left of text */
int newY; /* Y-coordinate from top of text */
{
register Tk_Window tkwin = childPtr->tkwin;
if (tkwin == NULL)
return;
if (childPtr->width > 0 && childPtr->height > 0) {
register int x, y;
x = childPtr->x - newX, y = childPtr->y - newY;
if (x != Tk_X (tkwin) || y != Tk_Y (tkwin) ||
childPtr->width != Tk_Width (tkwin) ||
childPtr->height != Tk_Height (tkwin)) {
Tk_MoveResizeWindow (tkwin, x, y,
(unsigned int)childPtr->width,
(unsigned int)childPtr->height);
if (!Tk_IsMapped (tkwin)) {
Tk_MapWindow (tkwin);
childPtr->flags |= MAPPED;
}
}
} else {
if (Tk_IsMapped (tkwin)) {
Tk_UnmapWindow (tkwin);
childPtr->flags &= ~MAPPED;
}
}
}
/*
* ----------------------------------------------------------------------
*
* 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 (textPtr)
Hypertext *textPtr;
{
int first, last;
int top, bottom;
top = textPtr->newY;
/* First line */
first = LineSearch (textPtr, top, 0, textPtr->numLines - 1);
if (first < 0) {
/* This can't be. The newY offset must be corrupted. */
fprintf (stderr, "First position not found `%d'", top);
return (TCL_ERROR);
}
textPtr->first = first;
/*
* If there is less text than window space, the bottom line is the last
* line of text. Otherwise search for the line located at the bottom of
* the window.
*/
bottom = top + Tk_Height (textPtr->tkwin) - 1;
if (bottom > textPtr->height) {
last = textPtr->numLines - 1;
} else {
last = LineSearch (textPtr, bottom, first, textPtr->numLines - 1);
}
if (last < 0) {
/* This can't be. The newY offset must be corrupted. */
fprintf (stderr, "Last position not found `%d'", bottom);
return (TCL_ERROR);
}
textPtr->last = last;
return (TCL_OK);
}
/*
* ----------------------------------------------------------------------
*
* LineSearch --
*
* Performs a binary search for the line located at some world
* Y coordinate. 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
LineSearch (textPtr, position, low, high)
Hypertext *textPtr;
int position;
register int low;
register int high;
{
register int mid;
register Line *linePtr;
while (low <= high) {
mid = (low + high) >> 1;
linePtr = textPtr->lineArr[mid];
if (position < linePtr->offset)
high = mid - 1;
else if (position >= (linePtr->offset + linePtr->height))
low = mid + 1;
else
return (mid);
}
return (-1);
}
/*
* ----------------------------------------------------------------------
*
* TextUpdateScrollBar --
*
* Invoke a Tcl command to the scrollbar, defining the new position
* and length of the scroll. See the Tk documentation for further
* information on the scrollbar. It is assumed the scrollbar command
* prefix is valid.
*
* Results:
* None.
*
* Side Effects:
* Scrollbar is commanded to change position and/or size.
* ----------------------------------------------------------------------
*/
static void
TextUpdateScrollBar (interp, command, total, window, first, units)
Tcl_Interp *interp;
char *command; /* scrollbar command */
int total; /* Total distance */
int window; /* Window distance */
int first; /* Position of viewport */
int units; /* Unit distance */
{
char cmdbuf[BUFSIZ];
int totalUnits, windowUnits;
int firstUnit, lastUnit;
totalUnits = (total / units) + 1;
windowUnits = window / units;
firstUnit = first / units;
lastUnit = (firstUnit + windowUnits);
if (firstUnit >= totalUnits)
firstUnit = totalUnits;
if (lastUnit > totalUnits)
lastUnit = totalUnits;
sprintf (cmdbuf, "%s %d %d %d %d", command, totalUnits, windowUnits,
firstUnit, lastUnit);
if (Tcl_Eval (interp, cmdbuf, 0, NULL) != TCL_OK) {
TkBindError (interp);
}
}
/*
* ----------------------------------------------------------------------
*
* ParseText --
*
* Parse the characters in the text field of the hypertext structure
* into an array of lines.
*
* Results:
* Returns TCL_OK or error depending if the file was read correctly.
*
* ----------------------------------------------------------------------
*/
static int
ParseText (interp, textPtr)
Tcl_Interp *interp;
Hypertext *textPtr;
{
register Line *linePtr;
int c;
#define HUGE_LINE_SIZE 1024
char buf[HUGE_LINE_SIZE];
#define HUGE_COMMAND_SIZE 10000
char cmdBuf[HUGE_COMMAND_SIZE];
int curPos;
register int cnt;
register int state;
int result = TCL_ERROR;
FreeLines (textPtr); /* Delete any previous lines */
CreateTraces (textPtr); /* Create variable traces */
linePtr = GetLine (textPtr);
if (linePtr == NULL)
goto error; /* Error allocating line */
state = cnt = 0;
curPos = 0;
while ((c = textPtr->text[curPos++]) != '\0') {
if (c == textPtr->specChar) {
state++;
} else if (c == '\n') {
state = -1;
} else if ((state == 0) && (c == '\\')) {
state = 3;
} else {
state = 0;
}
switch (state) {
case 2: /* Tcl Command block found */
cnt--;
if (GetTclCommand (textPtr, &curPos, cmdBuf) == NULL)
goto error;
linePtr->textLength = cnt;
if (Tcl_Eval (interp, cmdBuf, 0, NULL) != TCL_OK)
goto error;
state = 0;
break;
case 4: /* Escaped block designator */
buf[cnt - 1] = c;
state = 0;
break;
case -1: /* End of text line */
buf[cnt] = '\0';
SetLineText (linePtr, buf, cnt);
linePtr = GetLine (textPtr);
if (linePtr == NULL)
goto error;
cnt = state = 0;
break;
default: /* Default action, add to text buffer */
buf[cnt++] = c;
break;
}
if (cnt == HUGE_LINE_SIZE) {
interp->result = "Text line is too long";
goto error;
}
}
if (cnt > 0) {
buf[cnt] = '\0';
SetLineText (linePtr, buf, cnt);
}
result = TCL_OK;
error:
if (textPtr->text != NULL)
free (textPtr->text);
textPtr->text = NULL;
DeleteTraces (textPtr);
if (result == TCL_ERROR) {
FreeLines (textPtr);
return (TCL_ERROR);
} else {
AdjustLinesAllocated (textPtr);
textPtr->first = 0;
textPtr->last = textPtr->numLines - 1;
textPtr->newX = textPtr->newY = 0;
textPtr->width = textPtr->height = textPtr->x = textPtr->y = 0;
return (TCL_OK);
}
}
static int
ReadFile (interp, textPtr)
Tcl_Interp *interp;
Hypertext *textPtr;
{
FILE *fp;
register int arraySize = BUFSIZ;
register int numBytes = 0;
register char *charArr;
int numBytesRead = 0;
fp = fopen (textPtr->fileName, "r");
if (fp == NULL) {
Tcl_AppendResult (interp, "fopen: ", sys_errlist[errno],
": Can't open \"", textPtr->fileName,
"\" for reading", NULL);
return (TCL_ERROR);
}
charArr = malloc (arraySize);
if (charArr == NULL) {
Tcl_AppendResult (interp, "malloc: ", sys_errlist[errno],
": Can't alloc space for \"", textPtr->fileName,
"\" charArr", NULL);
return (TCL_ERROR);
}
for (;;) {
/* Read in next block of text */
numBytes = fread (&charArr[numBytesRead], sizeof (char), BUFSIZ, fp);
if (numBytes < 0)
goto error;
else if (numBytes == 0)
break;
numBytesRead += numBytes;
if (numBytesRead == arraySize) {
/* Reallocate with double the buffer size */
arraySize += arraySize;
charArr = reallocate (charArr, arraySize, numBytesRead);
if (charArr == NULL)
goto error;
}
}
charArr[numBytesRead] = '\0';
textPtr->text = charArr;
fclose (fp);
return (TCL_OK);
error:
fclose (fp);
return (TCL_ERROR);
}
static char *
GetTclCommand (textPtr, curPos, newCommand)
Hypertext *textPtr;
int *curPos;
char *newCommand;
{
register int c;
register int state;
register int src, dest;
state = 0;
dest = 0;
src = *curPos;
/* Simply collect the all the characters until %% into a buffer */
while ((c = textPtr->text[src++]) != '\0') {
if (c == textPtr->specChar) {
state++;
} else if ((state == 0) && (c == '\\')) {
state = 3;
} else {
state = 0;
}
switch (state) {
case 2: /* End of command block found */
newCommand[dest - 1] = '\0';
*curPos = src;
return (newCommand);
case 4: /* Escaped block designator */
newCommand[dest] = c;
state = 0;
break;
default: /* Add to command buffer */
newCommand[dest++] = c;
break;
}
if (dest == HUGE_COMMAND_SIZE) {
textPtr->interp->result = "Command block is too long";
return (NULL);
}
}
textPtr->interp->result = "Premature end of TCL command block";
return (NULL);
}
static void
FreeLines (textPtr)
Hypertext *textPtr;
{
register int i;
for (i = 0; i < textPtr->numLines; i++) {
DestroyLine (textPtr->lineArr[i]);
}
if (textPtr->lineArr != NULL)
free ((char *)textPtr->lineArr);
textPtr->lineArr = NULL;
textPtr->arraySize = textPtr->numLines = 0;
}
static void
AdjustLinesAllocated (textPtr)
Hypertext *textPtr;
{
if (textPtr->arraySize > 0) {
Line **newPtr;
newPtr = (Line **) reallocate ((char *)textPtr->lineArr,
textPtr->numLines * sizeof (Line *),
textPtr->arraySize * sizeof (Line *));
textPtr->arraySize = textPtr->numLines;
textPtr->lineArr = newPtr;
}
}
static void
SetLineText (linePtr, text, size)
Line *linePtr;
char *text;
int size;
{
linePtr->textLength = size;
linePtr->text = (char *)malloc (size + 1);
strcpy (linePtr->text, text);
}
/* ARGSUSED */
static char *
FileVarProc (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. */
{
Hypertext *textPtr = (Hypertext *) clientData;
Hypertext *lastTextPtr;
/* Check to see of this is the most recent trace */
lastTextPtr = (Hypertext *) Tcl_VarTraceInfo (interp, name1, flags,
FileVarProc,
(ClientData) NULL);
if (lastTextPtr != textPtr)
return (NULL); /* Ignore all but most current trace */
if (flags & TCL_TRACE_READS) {
if (strcmp (name1, "thisFile") == 0) {
if (textPtr->fileName == NULL)
Tcl_SetVar (interp, name1, "", flags);
else
Tcl_SetVar (interp, name1, textPtr->fileName, flags);
} else if (strcmp (name1, "thisLine") == 0) {
char buf[80];
sprintf (buf, "%d", textPtr->numLines);
Tcl_SetVar (interp, name1, buf, flags);
} else if (strcmp (name1, "this") == 0) {
Tcl_SetVar (interp, name1, Tk_PathName (textPtr->tkwin), flags);
} else {
return ("Unknown variable");
}
}
return (NULL);
}
static void
CreateTraces (textPtr)
Hypertext *textPtr;
{
register Tcl_Interp *interp = textPtr->interp;
int flags = (TCL_GLOBAL_ONLY | TCL_TRACE_READS);
Tcl_TraceVar (interp, "this", flags, FileVarProc, (ClientData) textPtr);
Tcl_TraceVar (interp, "thisLine", flags, FileVarProc, (ClientData) textPtr);
Tcl_TraceVar (interp, "thisFile", flags, FileVarProc, (ClientData) textPtr);
/* Make the traced variables global to the widget */
Tcl_Eval (interp, "global this thisFile thisLine", 0, (char **)NULL);
}
static void
DeleteTraces (textPtr)
Hypertext *textPtr;
{
register Tcl_Interp *interp = textPtr->interp;
int flags = (TCL_GLOBAL_ONLY | TCL_TRACE_READS);
/* Destroy the current variable traces */
Tcl_UntraceVar (interp, "thisFile", flags, FileVarProc,
(ClientData) textPtr);
Tcl_UntraceVar (interp, "thisLine", flags, FileVarProc,
(ClientData) textPtr);
Tcl_UntraceVar (interp, "this", flags, FileVarProc,
(ClientData) textPtr);
}
/*
* ----------------------------------------------------------------------
*
* FindChild --
* Searches for a child widget matching the pattern given by
* *childName*. If found, the pointer to the child structure is
* returned, otherwise NULL.
*
* Results:
* The pointer to the child structure. If not found, NULL.
*
* ----------------------------------------------------------------------
*/
static Child *
FindChild (interp, textPtr, pathName)
Tcl_Interp *interp;
Hypertext *textPtr;
char *pathName;
{
register Child *childPtr;
register Line *linePtr;
register int cnt;
int relative;
relative = (*pathName != '.');
/* Try matching pattern *pathName* to child widget name */
for (cnt = 0; cnt < textPtr->numLines; cnt++) {
linePtr = textPtr->lineArr[cnt];
for (childPtr = linePtr->children; childPtr != NULL;
childPtr = childPtr->nextPtr) {
if (childPtr->tkwin != NULL) {
char *name;
name = (relative) ? Tk_Name (childPtr->tkwin)
: Tk_PathName (childPtr->tkwin);
if (Tcl_StringMatch (name, pathName))
return (childPtr);
}
}
}
return (NULL);
}
/*
*--------------------------------------------------------------
*
* ChildGeometryProc --
*
* This procedure is invoked by Tk_GeometryRequest for
* subwindows 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
ChildGeometryProc (clientData, tkwin)
ClientData clientData; /* Information about window that got new
* preferred geometry. */
Tk_Window tkwin; /* Other Tk-related information about the
* window. */
{
register Child *childPtr = (Child *) clientData;
if (childPtr->widthWanted == 0)
childPtr->width = Tk_ReqWidth (childPtr->tkwin);
if (childPtr->heightWanted == 0)
childPtr->height = Tk_ReqHeight (childPtr->tkwin);
if (childPtr->width != Tk_Width (childPtr->tkwin) ||
childPtr->height != Tk_Height (childPtr->tkwin)) {
EventuallyRedraw (childPtr->parent);
childPtr->parent->flags |= LAYOUT_NEEDED;
}
}
static void
SendExposeEvent (tkwin)
register Tk_Window tkwin;
{
enum {
DontPropagate = 0
};
XEvent event;
event.xexpose.type = Expose;
event.type = Expose;
event.xexpose.window = Tk_WindowId (tkwin);
event.xexpose.display = Tk_Display (tkwin);
event.xexpose.count = 0;
event.xexpose.x = 0;
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);
}
static char *
reallocate (oldPtr, newSize, oldSize)
char *oldPtr;
unsigned int newSize;
unsigned int oldSize;
{
register char *newPtr = NULL;
if (newSize > 0) {
/* Allocate a new chunk of memory */
newPtr = (char *)malloc (newSize);
if (newPtr == NULL) {
fprintf (stderr, "reallocate: line %d of `%s': %s\n\
Can't allocate object\n", __LINE__, __FILE__, sys_errlist[errno]);
exit (1);
}
/* Copy whatever the old contents are */
if (oldPtr != NULL && oldSize > 0)
bcopy (oldPtr, newPtr, MIN (oldSize, newSize));
/* and clear the new contents */
if (newSize > oldSize)
bzero (newPtr + oldSize, newSize - oldSize);
}
free (oldPtr);
return (newPtr);
}