1 /*
2  * bltHtext.c --
3  *
4  *	This module implements a hypertext widget for the BLT toolkit.
5  *
6  * Copyright 1991-1998 Lucent Technologies, Inc.
7  *
8  * Permission to use, copy, modify, and distribute this software and
9  * its documentation for any purpose and without fee is hereby
10  * granted, provided that the above copyright notice appear in all
11  * copies and that both that the copyright notice and warranty
12  * disclaimer appear in supporting documentation, and that the names
13  * of Lucent Technologies any of their entities not be used in
14  * advertising or publicity pertaining to distribution of the software
15  * without specific, written prior permission.
16  *
17  * Lucent Technologies disclaims all warranties with regard to this
18  * software, including all implied warranties of merchantability and
19  * fitness.  In no event shall Lucent Technologies be liable for any
20  * special, indirect or consequential damages or any damages
21  * whatsoever resulting from loss of use, data or profits, whether in
22  * an action of contract, negligence or other tortuous action, arising
23  * out of or in connection with the use or performance of this
24  * software.
25  *
26  * The "htext" widget was created by George Howlett.
27  */
28 
29 /*
30  * To do:
31  *
32  * 1) Fix scroll unit round off errors.
33  *
34  * 2) Better error checking.
35  *
36  * 3) Use html format.
37  *
38  * 4) The dimension of cavities using -relwidth and -relheight
39  *    should be 0 when computing initial estimates for the size
40  *    of the virtual text.
41  */
42 
43 #include "bltInt.h"
44 
45 #ifndef NO_HTEXT
46 #include <bltChain.h>
47 #include <bltHash.h>
48 #include "bltTile.h"
49 
50 #include <sys/stat.h>
51 #include <X11/Xatom.h>
52 
53 #define DEF_LINES_ALLOC 512	/* Default block of lines allocated */
54 #define CLAMP(val,low,hi)	\
55 	(((val) < (low)) ? (low) : ((val) > (hi)) ? (hi) : (val))
56 
57 /*
58  * Justify option values
59  */
60 typedef enum {
61     JUSTIFY_CENTER, JUSTIFY_TOP, JUSTIFY_BOTTOM
62 } Justify;
63 
64 extern Tk_CustomOption bltFillOption;
65 extern Tk_CustomOption bltPadOption;
66 extern Tk_CustomOption bltDistanceOption;
67 extern Tk_CustomOption bltTileOption;
68 
69 static int StringToWidth _ANSI_ARGS_((ClientData clientData,
70 	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
71 	int flags));
72 static int StringToHeight _ANSI_ARGS_((ClientData clientData,
73 	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
74 	int flags));
75 static char *WidthHeightToString _ANSI_ARGS_((ClientData clientData,
76 	Tk_Window tkwin, char *widgRec, int offset, Tcl_FreeProc **freeProc));
77 
78 static Tk_CustomOption widthOption =
79 {
80     StringToWidth, WidthHeightToString, (ClientData)0
81 };
82 
83 static Tk_CustomOption heightOption =
84 {
85     StringToHeight, WidthHeightToString, (ClientData)0
86 };
87 
88 static int StringToJustify _ANSI_ARGS_((ClientData clientData,
89 	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
90 	int offset));
91 static char *JustifyToString _ANSI_ARGS_((ClientData clientData,
92 	Tk_Window tkwin, char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));
93 
94 static Tk_CustomOption justifyOption =
95 {
96     StringToJustify, JustifyToString, (ClientData)0
97 };
98 
99 
100 static void EmbeddedWidgetGeometryProc _ANSI_ARGS_((ClientData, Tk_Window));
101 static void EmbeddedWidgetCustodyProc _ANSI_ARGS_((ClientData, Tk_Window));
102 
103 static Tk_GeomMgr htextMgrInfo =
104 {
105     "htext",			/* Name of geometry manager used by winfo */
106     EmbeddedWidgetGeometryProc,	/* Procedure to for new geometry requests */
107     EmbeddedWidgetCustodyProc,	/* Procedure when window is taken away */
108 };
109 
110 
111 /*
112  * Line --
113  *
114  *	Structure to contain the contents of a single line of text and
115  *	the widgets on that line.
116  *
117  * 	Individual lines are not configurable, although changes to the
118  * 	size of widgets do effect its values.
119  */
120 typedef struct {
121     int offset;			/* Offset of line from y-origin (0) in
122 				 * world coordinates */
123     int baseline;		/* Baseline y-coordinate of the text */
124     short int width, height;	/* Dimensions of the line */
125     int textStart, textEnd;	/* Start and end indices of characters
126 				 * forming the line in the text array */
127     Blt_Chain *chainPtr;	/* Chain of embedded widgets on the line of
128 				 * text */
129 } Line;
130 
131 typedef struct {
132     int textStart;
133     int textEnd;
134 } Segment;
135 
136 typedef struct {
137     int x, y;
138 } Position;
139 
140 /*
141  * Hypertext widget.
142  */
143 typedef struct {
144     Tk_Window tkwin;		/* Window that embodies the widget.
145                                  * NULL means that the window has been
146                                  * destroyed but the data structures
147                                  * haven't yet been cleaned up.*/
148     Display *display;		/* Display containing widget; needed,
149                                  * among other things, to release
150                                  * resources after tkwin has already
151                                  * gone away. */
152     Tcl_Interp *interp;		/* Interpreter associated with widget. */
153 
154     Tcl_Command cmdToken;	/* Token for htext's widget command. */
155     int flags;
156 
157     /* User-configurable fields */
158 
159     XColor *normalFg, *normalBg;
160     Tk_Font font;		/* Font for normal text. May affect the size
161 				 * of the viewport if the width/height is
162 				 * specified in columns/rows */
163     GC drawGC;			/* Graphics context for normal text */
164     Blt_Tile tile;
165     int tileOffsetPage;		/* Set tile offset to top of page instead
166 				 * of toplevel window */
167     GC fillGC;			/* GC for clearing the window in the
168 				 * designated background color. The
169 				 * background color is the foreground
170 				 * attribute in GC.  */
171 
172     int nRows, nColumns;	/* # of characters of the current font
173 				 * for a row or column of the viewport.
174 				 * Used to determine the width and height
175 				 * of the text window (i.e. viewport) */
176     int reqWidth, reqHeight;	/* Requested dimensions of the viewport */
177     int maxWidth, maxHeight;	/* Maximum dimensions allowed for the viewport,
178 				 * regardless of the size of the text */
179 
180     Tk_Cursor cursor;		/* X Cursor */
181 
182     char *fileName;		/* If non-NULL, indicates the name of a
183 				 * hypertext file to be read into the widget.
184 				 * If NULL, the *text* field is considered
185 				 * instead */
186     char *text;			/* Hypertext to be loaded into the widget. This
187 				 * value is ignored if *fileName* is non-NULL */
188     int specChar;		/* Special character designating a TCL
189 			         * command block in a hypertext file. */
190     int leader;			/* # of pixels between lines */
191 
192     char *yScrollCmdPrefix;	/* Name of vertical scrollbar to invoke */
193     int yScrollUnits;		/* # of pixels per vertical scroll */
194     char *xScrollCmdPrefix;	/* Name of horizontal scroll bar to invoke */
195     int xScrollUnits;		/* # of pixels per horizontal scroll */
196 
197     int reqLineNum;		/* Line requested by "goto" command */
198 
199     /*
200      * The view port is the width and height of the window and the
201      * origin of the viewport (upper left corner) in world coordinates.
202      */
203     int worldWidth, worldHeight;/* Size of view text in world coordinates */
204     int xOffset, yOffset;	/* Position of viewport in world coordinates */
205 
206     int pendingX, pendingY;	/* New upper-left corner (origin) of
207 				 * the viewport (not yet posted) */
208 
209     int first, last;		/* Range of lines displayed */
210 
211     int lastWidth, lastHeight;
212     /* Last known size of the window: saved to
213 				 * recognize when the viewport is resized. */
214 
215     Blt_HashTable widgetTable;	/* Table of embedded widgets. */
216 
217     /*
218      * Selection display information:
219      */
220     Tk_3DBorder selBorder;	/* Border and background color */
221     int selBorderWidth;		/* Border width */
222     XColor *selFgColor;		/* Text foreground color */
223     GC selectGC;		/* GC for drawing selected text */
224     int selAnchor;		/* Fixed end of selection
225 			         * (i.e. "selection to" operation will
226 			         * use this as one end of the selection).*/
227     int selFirst;		/* The index of first character in the
228 				 * text array selected */
229     int selLast;		/* The index of the last character selected */
230     int exportSelection;	/* Non-zero means to export the internal text
231 				 * selection to the X server. */
232     char *takeFocus;
233 
234     /*
235      * Scanning information:
236      */
237     XPoint scanMark;		/* Anchor position of scan */
238     XPoint scanPt;		/* x,y position where the scan started. */
239 
240     char *charArr;		/* Pool of characters representing the text
241 				 * to be displayed */
242     int nChars;			/* Length of the text pool */
243 
244     Line *lineArr;		/* Array of pointers to text lines */
245     int nLines;			/* # of line entered into array. */
246     int arraySize;		/* Size of array allocated. */
247 
248 } HText;
249 
250 /*
251  * Bit flags for the hypertext widget:
252  */
253 #define REDRAW_PENDING	 (1<<0)	/* A DoWhenIdle handler has already
254 				 * been queued to redraw the window */
255 #define IGNORE_EXPOSURES (1<<1)	/* Ignore exposure events in the text
256 				 * window.  Potentially many expose
257 				 * events can occur while rearranging
258 				 * embedded widgets during a single call to
259 				 * the DisplayText.  */
260 
261 #define REQUEST_LAYOUT 	(1<<4)	/* Something has happened which
262 				 * requires the layout of text and
263 				 * embedded widget positions to be
264 				 * recalculated.  The following
265 				 * actions may cause this:
266 				 *
267 				 * 1) the contents of the hypertext
268 				 *    has changed by either the -file or
269 				 *    -text options.
270 				 *
271 				 * 2) a text attribute has changed
272 				 *    (line spacing, font, etc)
273 				 *
274 				 * 3) a embedded widget has been resized or
275 				 *    moved.
276 				 *
277 				 * 4) a widget configuration option has
278 				 *    changed.
279 				 */
280 #define TEXT_DIRTY 	(1<<5)	/* The layout was recalculated and the
281 				 * size of the world (text layout) has
282 				 * changed. */
283 #define GOTO_PENDING 	(1<<6)	/* Indicates the starting text line
284 				 * number has changed. To be reflected
285 				 * the next time the widget is redrawn. */
286 #define WIDGET_APPENDED	(1<<7)	/* Indicates a embedded widget has just
287 				 * been appended to the text.  This is
288 				 * used to determine when to add a
289 				 * space to the text array */
290 
291 #define DEF_HTEXT_BACKGROUND		STD_NORMAL_BACKGROUND
292 #define DEF_HTEXT_BG_MONO		STD_NORMAL_BG_MONO
293 #define DEF_HTEXT_CURSOR		"arrow"
294 #define DEF_HTEXT_EXPORT_SELECTION	"1"
295 
296 #define DEF_HTEXT_FOREGROUND		STD_NORMAL_FOREGROUND
297 #define DEF_HTEXT_FG_MONO		STD_NORMAL_FG_MONO
298 #define DEF_HTEXT_FILE_NAME		(char *)NULL
299 #define DEF_HTEXT_FONT			STD_FONT
300 #define DEF_HTEXT_HEIGHT		"0"
301 #define DEF_HTEXT_LINE_SPACING		"1"
302 #define DEF_HTEXT_MAX_HEIGHT		(char *)NULL
303 #define DEF_HTEXT_MAX_WIDTH 		(char *)NULL
304 #define DEF_HTEXT_SCROLL_UNITS		"10"
305 #define DEF_HTEXT_SPEC_CHAR		"0x25"
306 #define DEF_HTEXT_SELECT_BORDERWIDTH 	STD_SELECT_BORDERWIDTH
307 #define DEF_HTEXT_SELECT_BACKGROUND 	STD_SELECT_BACKGROUND
308 #define DEF_HTEXT_SELECT_BG_MONO  	STD_SELECT_BG_MONO
309 #define DEF_HTEXT_SELECT_FOREGROUND 	STD_SELECT_FOREGROUND
310 #define DEF_HTEXT_SELECT_FG_MONO  	STD_SELECT_FG_MONO
311 #define DEF_HTEXT_TAKE_FOCUS		"1"
312 #define DEF_HTEXT_TEXT			(char *)NULL
313 #define DEF_HTEXT_TILE_OFFSET		"1"
314 #define DEF_HTEXT_WIDTH			"0"
315 
316 static Tk_ConfigSpec configSpecs[] =
317 {
318     {TK_CONFIG_COLOR, "-background", "background", "Background",
319 	DEF_HTEXT_BACKGROUND, Tk_Offset(HText, normalBg), TK_CONFIG_COLOR_ONLY},
320     {TK_CONFIG_COLOR, "-background", "background", "Background",
321 	DEF_HTEXT_BG_MONO, Tk_Offset(HText, normalBg), TK_CONFIG_MONO_ONLY},
322     {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
323     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
324 	DEF_HTEXT_CURSOR, Tk_Offset(HText, cursor), TK_CONFIG_NULL_OK},
325     {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection", "ExportSelection",
326 	DEF_HTEXT_EXPORT_SELECTION, Tk_Offset(HText, exportSelection), 0},
327     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
328     {TK_CONFIG_STRING, "-file", "file", "File",
329 	DEF_HTEXT_FILE_NAME, Tk_Offset(HText, fileName), TK_CONFIG_NULL_OK},
330     {TK_CONFIG_FONT, "-font", "font", "Font",
331 	DEF_HTEXT_FONT, Tk_Offset(HText, font), 0},
332     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
333 	DEF_HTEXT_FOREGROUND, Tk_Offset(HText, normalFg), TK_CONFIG_COLOR_ONLY},
334     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
335 	DEF_HTEXT_FG_MONO, Tk_Offset(HText, normalFg), TK_CONFIG_MONO_ONLY},
336     {TK_CONFIG_CUSTOM, "-height", "height", "Height",
337 	DEF_HTEXT_HEIGHT, Tk_Offset(HText, reqHeight),
338 	TK_CONFIG_DONT_SET_DEFAULT, &heightOption},
339     {TK_CONFIG_CUSTOM, "-linespacing", "lineSpacing", "LineSpacing",
340 	DEF_HTEXT_LINE_SPACING, Tk_Offset(HText, leader),
341 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
342     {TK_CONFIG_CUSTOM, "-maxheight", "maxHeight", "MaxHeight",
343 	DEF_HTEXT_MAX_HEIGHT, Tk_Offset(HText, maxHeight),
344 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
345     {TK_CONFIG_CUSTOM, "-maxwidth", "maxWidth", "MaxWidth",
346 	DEF_HTEXT_MAX_WIDTH, Tk_Offset(HText, maxWidth),
347 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
348     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
349 	DEF_HTEXT_SELECT_BG_MONO, Tk_Offset(HText, selBorder),
350 	TK_CONFIG_MONO_ONLY},
351     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Background",
352 	DEF_HTEXT_SELECT_BACKGROUND, Tk_Offset(HText, selBorder),
353 	TK_CONFIG_COLOR_ONLY},
354     {TK_CONFIG_CUSTOM, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
355 	DEF_HTEXT_SELECT_BORDERWIDTH, Tk_Offset(HText, selBorderWidth),
356 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
357     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
358 	DEF_HTEXT_SELECT_FG_MONO, Tk_Offset(HText, selFgColor),
359 	TK_CONFIG_MONO_ONLY},
360     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground",
361 	DEF_HTEXT_SELECT_FOREGROUND, Tk_Offset(HText, selFgColor),
362 	TK_CONFIG_COLOR_ONLY},
363     {TK_CONFIG_INT, "-specialchar", "specialChar", "SpecialChar",
364 	DEF_HTEXT_SPEC_CHAR, Tk_Offset(HText, specChar), 0},
365     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
366 	DEF_HTEXT_TAKE_FOCUS, Tk_Offset(HText, takeFocus),
367 	TK_CONFIG_NULL_OK},
368     {TK_CONFIG_CUSTOM, "-tile", "tile", "Tile",
369 	(char *)NULL, Tk_Offset(HText, tile), TK_CONFIG_NULL_OK,
370 	&bltTileOption},
371     {TK_CONFIG_BOOLEAN, "-tileoffset", "tileOffset", "TileOffset",
372 	DEF_HTEXT_TILE_OFFSET, Tk_Offset(HText, tileOffsetPage), 0},
373     {TK_CONFIG_STRING, "-text", "text", "Text",
374 	DEF_HTEXT_TEXT, Tk_Offset(HText, text), TK_CONFIG_NULL_OK},
375     {TK_CONFIG_CUSTOM, "-width", "width", "Width",
376 	DEF_HTEXT_WIDTH, Tk_Offset(HText, reqWidth),
377 	TK_CONFIG_DONT_SET_DEFAULT, &widthOption},
378     {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
379 	(char *)NULL, Tk_Offset(HText, xScrollCmdPrefix), TK_CONFIG_NULL_OK},
380     {TK_CONFIG_CUSTOM, "-xscrollunits", "xScrollUnits", "ScrollUnits",
381 	DEF_HTEXT_SCROLL_UNITS, Tk_Offset(HText, xScrollUnits),
382 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
383     {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
384 	(char *)NULL, Tk_Offset(HText, yScrollCmdPrefix), TK_CONFIG_NULL_OK},
385     {TK_CONFIG_CUSTOM, "-yscrollunits", "yScrollUnits", "yScrollUnits",
386 	DEF_HTEXT_SCROLL_UNITS, Tk_Offset(HText, yScrollUnits),
387 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
388     {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
389 	(char *)NULL, 0, 0}
390 };
391 
392 typedef struct {
393     HText *htPtr;		/* Pointer to parent's Htext structure */
394     Tk_Window tkwin;		/* Widget window */
395     int flags;
396 
397     int x, y;			/* Origin of embedded widget in text */
398 
399     int cavityWidth, cavityHeight; /* Dimensions of the cavity
400 				    * surrounding the embedded widget */
401     /*
402      *  Dimensions of the embedded widget.  Compared against actual
403      *	embedded widget sizes when checking for resizing.
404      */
405     int winWidth, winHeight;
406 
407     int precedingTextEnd;	/* Index (in charArr) of the the last
408 				 * character immediatedly preceding
409 				 * the embedded widget */
410     int precedingTextWidth;	/* Width of normal text preceding widget. */
411 
412     Tk_Anchor anchor;
413     Justify justify;		/* Justification of region wrt to line */
414 
415     /*
416      * Requested dimensions of the cavity (includes padding). If non-zero,
417      * it overrides the calculated dimension of the cavity.
418      */
419     int reqCavityWidth, reqCavityHeight;
420 
421     /*
422      * Relative dimensions of cavity wrt the size of the viewport. If
423      * greater than 0.0.
424      */
425     double relCavityWidth, relCavityHeight;
426 
427     int reqWidth, reqHeight;	/* If non-zero, overrides the requested
428 				 * dimension of the embedded widget */
429 
430     double relWidth, relHeight;	/* Relative dimensions of embedded
431 				 * widget wrt the size of the viewport */
432 
433     Blt_Pad padX, padY;		/* Extra padding to frame around */
434 
435     int ipadX, ipadY;		/* internal padding for window */
436 
437     int fill;			/* Fill style flag */
438 
439 } EmbeddedWidget;
440 
441 /*
442  * Flag bits embedded widgets:
443  */
444 #define WIDGET_VISIBLE	(1<<2)	/* Widget is currently visible in the
445 				 * viewport. */
446 #define WIDGET_NOT_CHILD (1<<3) /* Widget is not a child of hypertext. */
447 /*
448  * Defaults for embedded widgets:
449  */
450 #define DEF_WIDGET_ANCHOR        "center"
451 #define DEF_WIDGET_FILL		"none"
452 #define DEF_WIDGET_HEIGHT	"0"
453 #define DEF_WIDGET_JUSTIFY	"center"
454 #define DEF_WIDGET_PAD_X		"0"
455 #define DEF_WIDGET_PAD_Y		"0"
456 #define DEF_WIDGET_REL_HEIGHT	"0.0"
457 #define DEF_WIDGET_REL_WIDTH  	"0.0"
458 #define DEF_WIDGET_WIDTH  	"0"
459 
460 static Tk_ConfigSpec widgetConfigSpecs[] =
461 {
462     {TK_CONFIG_ANCHOR, "-anchor", (char *)NULL, (char *)NULL,
463 	DEF_WIDGET_ANCHOR, Tk_Offset(EmbeddedWidget, anchor),
464 	TK_CONFIG_DONT_SET_DEFAULT},
465     {TK_CONFIG_CUSTOM, "-fill", (char *)NULL, (char *)NULL,
466 	DEF_WIDGET_FILL, Tk_Offset(EmbeddedWidget, fill),
467 	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
468     {TK_CONFIG_CUSTOM, "-cavityheight", (char *)NULL, (char *)NULL,
469 	DEF_WIDGET_HEIGHT, Tk_Offset(EmbeddedWidget, reqCavityHeight),
470 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
471     {TK_CONFIG_CUSTOM, "-cavitywidth", (char *)NULL, (char *)NULL,
472 	DEF_WIDGET_WIDTH, Tk_Offset(EmbeddedWidget, reqCavityWidth),
473 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
474     {TK_CONFIG_CUSTOM, "-height", (char *)NULL, (char *)NULL,
475 	DEF_WIDGET_HEIGHT, Tk_Offset(EmbeddedWidget, reqHeight),
476 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
477     {TK_CONFIG_CUSTOM, "-justify", (char *)NULL, (char *)NULL,
478 	DEF_WIDGET_JUSTIFY, Tk_Offset(EmbeddedWidget, justify),
479 	TK_CONFIG_DONT_SET_DEFAULT, &justifyOption},
480     {TK_CONFIG_CUSTOM, "-padx", (char *)NULL, (char *)NULL,
481 	DEF_WIDGET_PAD_X, Tk_Offset(EmbeddedWidget, padX),
482 	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
483     {TK_CONFIG_CUSTOM, "-pady", (char *)NULL, (char *)NULL,
484 	DEF_WIDGET_PAD_Y, Tk_Offset(EmbeddedWidget, padY),
485 	TK_CONFIG_DONT_SET_DEFAULT, &bltPadOption},
486     {TK_CONFIG_DOUBLE, "-relcavityheight", (char *)NULL, (char *)NULL,
487 	DEF_WIDGET_REL_HEIGHT, Tk_Offset(EmbeddedWidget, relCavityHeight),
488 	TK_CONFIG_DONT_SET_DEFAULT},
489     {TK_CONFIG_DOUBLE, "-relcavitywidth", (char *)NULL, (char *)NULL,
490 	DEF_WIDGET_REL_WIDTH, Tk_Offset(EmbeddedWidget, relCavityWidth),
491 	TK_CONFIG_DONT_SET_DEFAULT},
492     {TK_CONFIG_DOUBLE, "-relheight", (char *)NULL, (char *)NULL,
493 	DEF_WIDGET_REL_HEIGHT, Tk_Offset(EmbeddedWidget, relHeight),
494 	TK_CONFIG_DONT_SET_DEFAULT},
495     {TK_CONFIG_DOUBLE, "-relwidth", (char *)NULL, (char *)NULL,
496 	DEF_WIDGET_REL_WIDTH, Tk_Offset(EmbeddedWidget, relWidth),
497 	TK_CONFIG_DONT_SET_DEFAULT},
498     {TK_CONFIG_CUSTOM, "-width", (char *)NULL, (char *)NULL,
499 	DEF_WIDGET_WIDTH, Tk_Offset(EmbeddedWidget, reqWidth),
500 	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
501     {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
502 	(char *)NULL, 0, 0}
503 };
504 
505 
506 /* Forward Declarations */
507 static void DestroyText _ANSI_ARGS_((DestroyData dataPtr));
508 static void EmbeddedWidgetEventProc _ANSI_ARGS_((ClientData clientdata,
509 	XEvent *eventPtr));
510 static void DisplayText _ANSI_ARGS_((ClientData clientData));
511 static void TextDeleteCmdProc _ANSI_ARGS_((ClientData clientdata));
512 
513 static Tcl_VarTraceProc TextVarProc;
514 static Blt_TileChangedProc TileChangedProc;
515 static Tk_LostSelProc TextLostSelection;
516 static Tk_SelectionProc TextSelectionProc;
517 static Tk_EventProc TextEventProc;
518 static Tcl_CmdProc TextWidgetCmd;
519 static Tcl_CmdProc TextCmd;
520 
521 static void widgetWorldChanged(ClientData clientData);
522 
523 static Tk_ClassProcs htextClass = {
524     sizeof(Tk_ClassProcs),	/* size */
525     widgetWorldChanged,		/* worldChangedProc */
526 };
527 
528 /* end of Forward Declarations */
529 
530 
531  /* Custom options */
532 /*
533  *----------------------------------------------------------------------
534  *
535  * StringToJustify --
536  *
537  * 	Converts the justification string into its numeric
538  * 	representation. This configuration option affects how the
539  *	embedded widget is positioned with respect to the line on which
540  *	it sits.
541  *
542  *	Valid style strings are:
543  *
544  *	"top"      Uppermost point of region is top of the line's
545  *		   text
546  * 	"center"   Center point of region is line's baseline.
547  *	"bottom"   Lowermost point of region is bottom of the
548  *		   line's text
549  *
550  * Returns:
551  *	A standard Tcl result.  If the value was not valid
552  *
553  *---------------------------------------------------------------------- */
554 /*ARGSUSED*/
555 static int
StringToJustify(clientData,interp,tkwin,string,widgRec,offset)556 StringToJustify(clientData, interp, tkwin, string, widgRec, offset)
557     ClientData clientData;	/* Not used. */
558     Tcl_Interp *interp;		/* Interpreter to send results back to */
559     Tk_Window tkwin;		/* Not used. */
560     char *string;		/* Justification string */
561     char *widgRec;		/* Structure record */
562     int offset;			/* Offset of justify in record */
563 {
564     Justify *justPtr = (Justify *)(widgRec + offset);
565     unsigned int length;
566     char c;
567 
568     c = string[0];
569     length = strlen(string);
570     if ((c == 'c') && (strncmp(string, "center", length) == 0)) {
571 	*justPtr = JUSTIFY_CENTER;
572     } else if ((c == 't') && (strncmp(string, "top", length) == 0)) {
573 	*justPtr = JUSTIFY_TOP;
574     } else if ((c == 'b') && (strncmp(string, "bottom", length) == 0)) {
575 	*justPtr = JUSTIFY_BOTTOM;
576     } else {
577 	Tcl_AppendResult(interp, "bad justification argument \"", string,
578 	    "\": should be \"center\", \"top\", or \"bottom\"", (char *)NULL);
579 	return TCL_ERROR;
580     }
581     return TCL_OK;
582 }
583 
584 /*
585  *----------------------------------------------------------------------
586  *
587  * NameOfJustify --
588  *
589  *	Returns the justification style string based upon the value.
590  *
591  * Results:
592  *	The static justification style string is returned.
593  *
594  *----------------------------------------------------------------------
595  */
596 static char *
NameOfJustify(justify)597 NameOfJustify(justify)
598     Justify justify;
599 {
600     switch (justify) {
601     case JUSTIFY_CENTER:
602 	return "center";
603     case JUSTIFY_TOP:
604 	return "top";
605     case JUSTIFY_BOTTOM:
606 	return "bottom";
607     default:
608 	return "unknown justification value";
609     }
610 }
611 
612 /*
613  *----------------------------------------------------------------------
614  *
615  * JustifyToString --
616  *
617  *	Returns the justification style string based upon the value.
618  *
619  * Results:
620  *	The justification style string is returned.
621  *
622  *----------------------------------------------------------------------
623  */
624 /*ARGSUSED*/
625 static char *
JustifyToString(clientData,tkwin,widgRec,offset,freeProcPtr)626 JustifyToString(clientData, tkwin, widgRec, offset, freeProcPtr)
627     ClientData clientData;	/* Not used. */
628     Tk_Window tkwin;		/* Not used. */
629     char *widgRec;		/* Structure record */
630     int offset;			/* Offset of justify record */
631     Tcl_FreeProc **freeProcPtr;	/* Not used. */
632 {
633     Justify justify = *(Justify *)(widgRec + offset);
634 
635     return NameOfJustify(justify);
636 }
637 
638 /*
639  *----------------------------------------------------------------------
640  *
641  * GetScreenDistance --
642  *
643  *	Converts the given string into the screen distance or number
644  *	of characters.  The valid formats are
645  *
646  *	    N	- pixels	Nm - millimeters
647  *	    Ni  - inches        Np - pica
648  *          Nc  - centimeters   N# - number of characters
649  *
650  *	where N is a non-negative decimal number.
651  *
652  * Results:
653  *	A standard Tcl result.  The screen distance and the number of
654  *	characters are returned.  If the string can't be converted,
655  *	TCL_ERROR is returned and interp->result will contain an error
656  *	message.
657  *
658  *----------------------------------------------------------------------
659  */
660 static int
GetScreenDistance(interp,tkwin,string,sizePtr,countPtr)661 GetScreenDistance(interp, tkwin, string, sizePtr, countPtr)
662     Tcl_Interp *interp;
663     Tk_Window tkwin;
664     char *string;
665     int *sizePtr;
666     int *countPtr;
667 {
668     int nPixels, nChars;
669     char *endPtr;		/* Pointer to last character scanned */
670     double value;
671     int rounded;
672 
673     value = strtod(string, &endPtr);
674     if (endPtr == string) {
675 	Tcl_AppendResult(interp, "bad screen distance \"", string, "\"",
676 	    (char *)NULL);
677 	return TCL_ERROR;
678     }
679     if (value < 0.0) {
680 	Tcl_AppendResult(interp, "screen distance \"", string,
681 	    "\" must be non-negative value", (char *)NULL);
682 	return TCL_ERROR;
683     }
684     while (isspace(UCHAR(*endPtr))) {
685 	if (*endPtr == '\0') {
686 	    break;
687 	}
688 	endPtr++;
689     }
690     nPixels = nChars = 0;
691     rounded = ROUND(value);
692     switch (*endPtr) {
693     case '\0':			/* Distance in pixels */
694 	nPixels = rounded;
695 	break;
696     case '#':			/* Number of characters */
697 	nChars = rounded;
698 	break;
699     default:			/* cm, mm, pica, inches */
700 	if (Tk_GetPixels(interp, tkwin, string, &rounded) != TCL_OK) {
701 	    return TCL_ERROR;
702 	}
703 	nPixels = rounded;
704 	break;
705     }
706     *sizePtr = nPixels;
707     *countPtr = nChars;
708     return TCL_OK;
709 }
710 
711 /*
712  *----------------------------------------------------------------------
713  *
714  * StringToHeight --
715  *
716  *	Like TK_CONFIG_PIXELS, but adds an extra check for negative
717  *	values.
718  *
719  *----------------------------------------------------------------------
720  */
721 /*ARGSUSED*/
722 static int
StringToHeight(clientData,interp,tkwin,string,widgRec,offset)723 StringToHeight(clientData, interp, tkwin, string, widgRec, offset)
724     ClientData clientData;	/* Not used. */
725     Tcl_Interp *interp;		/* Interpreter to send results back to */
726     Tk_Window tkwin;		/* Window */
727     char *string;		/* Pixel value string */
728     char *widgRec;		/* Widget record */
729     int offset;			/* Not used. */
730 {
731     HText *htPtr = (HText *)widgRec;
732     int height, nRows;
733 
734     if (GetScreenDistance(interp, tkwin, string, &height, &nRows) != TCL_OK) {
735 	return TCL_ERROR;
736     }
737     htPtr->nRows = nRows;
738     htPtr->reqHeight = height;
739     return TCL_OK;
740 }
741 
742 /*
743  *----------------------------------------------------------------------
744  *
745  * StringToWidth --
746  *
747  *	Like TK_CONFIG_PIXELS, but adds an extra check for negative
748  *	values.
749  *
750  *----------------------------------------------------------------------
751  */
752 /*ARGSUSED*/
753 static int
StringToWidth(clientData,interp,tkwin,string,widgRec,offset)754 StringToWidth(clientData, interp, tkwin, string, widgRec, offset)
755     ClientData clientData;	/* Not used. */
756     Tcl_Interp *interp;		/* Interpreter to send results back to */
757     Tk_Window tkwin;		/* Window */
758     char *string;		/* Pixel value string */
759     char *widgRec;		/* Widget record */
760     int offset;			/* Not used. */
761 {
762     HText *htPtr = (HText *)widgRec;
763     int width, nColumns;
764 
765     if (GetScreenDistance(interp, tkwin, string, &width,
766 	    &nColumns) != TCL_OK) {
767 	return TCL_ERROR;
768     }
769     htPtr->nColumns = nColumns;
770     htPtr->reqWidth = width;
771     return TCL_OK;
772 }
773 
774 /*
775  *----------------------------------------------------------------------
776  *
777  * WidthHeightToString --
778  *
779  *	Returns the string representing the positive pixel size.
780  *
781  * Results:
782  *	The pixel size string is returned.
783  *
784  *----------------------------------------------------------------------
785  */
786 /*ARGSUSED*/
787 static char *
WidthHeightToString(clientData,tkwin,widgRec,offset,freeProcPtr)788 WidthHeightToString(clientData, tkwin, widgRec, offset, freeProcPtr)
789     ClientData clientData;	/* Not used. */
790     Tk_Window tkwin;		/* Not used. */
791     char *widgRec;		/* Row/column structure record */
792     int offset;			/* Offset of fill in Partition record */
793     Tcl_FreeProc **freeProcPtr;	/* Not used. */
794 {
795     int pixels = *(int *)(widgRec + offset);
796     char *result;
797     char string[200];
798 
799     sprintf(string, "%d", pixels);
800     result = Blt_Strdup(string);
801     if (result == NULL) {
802 	return "out of memory";
803     }
804     *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
805     return result;
806 }
807 
808 /* General routines */
809 /*
810  *----------------------------------------------------------------------
811  *
812  * EventuallyRedraw --
813  *
814  *	Queues a request to redraw the text window at the next idle
815  *	point.
816  *
817  * Results:
818  *	None.
819  *
820  * Side effects:
821  *	Information gets redisplayed.  Right now we don't do selective
822  *	redisplays:  the whole window will be redrawn.  This doesn't
823  *	seem to hurt performance noticeably, but if it does then this
824  *	could be changed.
825  *
826  *----------------------------------------------------------------------
827  */
828 static void
EventuallyRedraw(htPtr)829 EventuallyRedraw(htPtr)
830     HText *htPtr;		/* Information about widget. */
831 {
832     if ((htPtr->tkwin != NULL) && !(htPtr->flags & REDRAW_PENDING)) {
833 	htPtr->flags |= REDRAW_PENDING;
834 	Tcl_DoWhenIdle(DisplayText, htPtr);
835     }
836 }
837 
838 
839 /*
840  * --------------------------------------------------------------------
841  *
842  * ResizeArray --
843  *
844  *	Reallocates memory to the new size given.  New memory
845  *	is also cleared (zeros).
846  *
847  * Results:
848  *	Returns a pointer to the new object or NULL if an error occurred.
849  *
850  * Side Effects:
851  *	Memory is re/allocated.
852  *
853  * --------------------------------------------------------------------
854  */
855 static int
ResizeArray(arrayPtr,elemSize,newSize,prevSize)856 ResizeArray(arrayPtr, elemSize, newSize, prevSize)
857     char **arrayPtr;
858     int elemSize;
859     int newSize;
860     int prevSize;
861 {
862     char *newPtr;
863 
864     if (newSize == prevSize) {
865 	return TCL_OK;
866     }
867     if (newSize == 0) {		/* Free entire array */
868 	Blt_Free(*arrayPtr);
869 	*arrayPtr = NULL;
870 	return TCL_OK;
871     }
872     newPtr = Blt_Calloc(elemSize, newSize);
873     if (newPtr == NULL) {
874 	return TCL_ERROR;
875     }
876     if ((prevSize > 0) && (*arrayPtr != NULL)) {
877 	int size;
878 
879 	size = MIN(prevSize, newSize) * elemSize;
880 	if (size > 0) {
881 	    memcpy(newPtr, *arrayPtr, size);
882 	}
883 	Blt_Free(*arrayPtr);
884     }
885     *arrayPtr = newPtr;
886     return TCL_OK;
887 }
888 
889 /*
890  * ----------------------------------------------------------------------
891  *
892  * LineSearch --
893  *
894  * 	Performs a binary search for the line of text located at some
895  * 	world y-coordinate (not screen y-coordinate). The search is
896  * 	inclusive of those lines from low to high.
897  *
898  * Results:
899  *	Returns the array index of the line found at the given
900  *	y-coordinate.  If the y-coordinate is outside of the given range
901  *	of lines, -1 is returned.
902  *
903  * ----------------------------------------------------------------------
904  */
905 static int
LineSearch(htPtr,yCoord,low,high)906 LineSearch(htPtr, yCoord, low, high)
907     HText *htPtr;		/* HText widget */
908     int yCoord;			/* Search y-coordinate  */
909     int low, high;		/* Range of lines to search */
910 {
911     int median;
912     Line *linePtr;
913 
914     while (low <= high) {
915 	median = (low + high) >> 1;
916 	linePtr = htPtr->lineArr + median;
917 	if (yCoord < linePtr->offset) {
918 	    high = median - 1;
919 	} else if (yCoord >= (linePtr->offset + linePtr->height)) {
920 	    low = median + 1;
921 	} else {
922 	    return median;
923 	}
924     }
925     return -1;
926 }
927 
928 /*
929  * ----------------------------------------------------------------------
930  *
931  * IndexSearch --
932  *
933  *	Try to find what line contains a given text index. Performs
934  *	a binary search for the text line which contains the given index.
935  *	The search is inclusive of those lines from low and high.
936  *
937  * Results:
938  *	Returns the line number containing the given index. If the index
939  *	is outside the range of lines, -1 is returned.
940  *
941  * ----------------------------------------------------------------------
942  */
943 static int
IndexSearch(htPtr,key,low,high)944 IndexSearch(htPtr, key, low, high)
945     HText *htPtr;		/* HText widget */
946     int key;			/* Search index */
947     int low, high;		/* Range of lines to search */
948 {
949     int median;
950     Line *linePtr;
951 
952     while (low <= high) {
953 	median = (low + high) >> 1;
954 	linePtr = htPtr->lineArr + median;
955 	if (key < linePtr->textStart) {
956 	    high = median - 1;
957 	} else if (key > linePtr->textEnd) {
958 	    low = median + 1;
959 	} else {
960 	    return median;
961 	}
962     }
963     return -1;
964 }
965 
966 /*
967  * ----------------------------------------------------------------------
968  *
969  * GetXYPosIndex --
970  *
971  * 	Converts a string in the form "@x,y", where x and y are
972  *	window coordinates, to a text index.
973  *
974  *	Window coordinates are first translated into world coordinates.
975  *	Any coordinate outside of the bounds of the virtual text is
976  *	silently set the nearest boundary.
977  *
978  * Results:
979  *	A standard Tcl result.  If "string" is a valid index, then
980  *	*indexPtr is filled with the numeric index corresponding.
981  *	Otherwise an error message is left in interp->result.
982  *
983  * ----------------------------------------------------------------------
984  */
985 static int
GetXYPosIndex(htPtr,string,indexPtr)986 GetXYPosIndex(htPtr, string, indexPtr)
987     HText *htPtr;
988     char *string;
989     int *indexPtr;
990 {
991     int x, y, curX, dummy;
992     int textLength, textStart;
993     int cindex, lindex;
994     Line *linePtr;
995 
996     if (Blt_GetXY(htPtr->interp, htPtr->tkwin, string, &x, &y) != TCL_OK) {
997 	return TCL_ERROR;
998     }
999     /* Locate the line corresponding to the window y-coordinate position */
1000 
1001     y += htPtr->yOffset;
1002     if (y < 0) {
1003 	lindex = htPtr->first;
1004     } else if (y >= htPtr->worldHeight) {
1005 	lindex = htPtr->last;
1006     } else {
1007 	lindex = LineSearch(htPtr, y, 0, htPtr->nLines - 1);
1008     }
1009     if (lindex < 0) {
1010 	Tcl_AppendResult(htPtr->interp, "can't find line at \"", string, "\"",
1011 	    (char *)NULL);
1012 	return TCL_ERROR;
1013     }
1014     x += htPtr->xOffset;
1015     if (x < 0) {
1016 	x = 0;
1017     } else if (x > htPtr->worldWidth) {
1018 	x = htPtr->worldWidth;
1019     }
1020     linePtr = htPtr->lineArr + lindex;
1021     curX = 0;
1022     textStart = linePtr->textStart;
1023     textLength = linePtr->textEnd - linePtr->textStart;
1024     if (Blt_ChainGetLength(linePtr->chainPtr) > 0) {
1025 	Blt_ChainLink *linkPtr;
1026 	int deltaX;
1027 	EmbeddedWidget *winPtr;
1028 
1029 	for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
1030 	    linkPtr = Blt_ChainNextLink(linkPtr)) {
1031 	    winPtr = Blt_ChainGetValue(linkPtr);
1032 	    deltaX = winPtr->precedingTextWidth + winPtr->cavityWidth;
1033 	    if ((curX + deltaX) > x) {
1034 		textLength = (winPtr->precedingTextEnd - textStart);
1035 		break;
1036 	    }
1037 	    curX += deltaX;
1038 	    /*
1039 	     * Skip over the trailing space. It designates the position of
1040 	     * a embedded widget in the text
1041 	     */
1042 	    textStart = winPtr->precedingTextEnd + 1;
1043 	}
1044     }
1045     cindex = Tk_MeasureChars(htPtr->font, htPtr->charArr + textStart,
1046 	textLength, 10000, DEF_TEXT_FLAGS, &dummy);
1047     *indexPtr = textStart + cindex;
1048     return TCL_OK;
1049 }
1050 
1051 /*
1052  *--------------------------------------------------------------
1053  *
1054  * ParseIndex --
1055  *
1056  *	Parse a string representing a text index into numeric
1057  *	value.  A text index can be in one of the following forms.
1058  *
1059  *	  "anchor"	- anchor position of the selection.
1060  *	  "sel.first"   - index of the first character in the selection.
1061  *	  "sel.last"	- index of the last character in the selection.
1062  *	  "page.top"  	- index of the first character on the page.
1063  *	  "page.bottom"	- index of the last character on the page.
1064  *	  "@x,y"	- x and y are window coordinates.
1065  * 	  "number	- raw index of text
1066  *	  "line.char"	- line number and character position
1067  *
1068  * Results:
1069  *	A standard Tcl result.  If "string" is a valid index, then
1070  *	*indexPtr is filled with the corresponding numeric index.
1071  *	Otherwise an error message is left in interp->result.
1072  *
1073  * Side effects:
1074  *	None.
1075  *
1076  *--------------------------------------------------------------
1077  */
1078 static int
ParseIndex(htPtr,string,indexPtr)1079 ParseIndex(htPtr, string, indexPtr)
1080     HText *htPtr;		/* Text for which the index is being
1081 				 * specified. */
1082     char *string;		/* Numerical index into htPtr's element
1083 				 * list, or "end" to refer to last element. */
1084     int *indexPtr;		/* Where to store converted relief. */
1085 {
1086     unsigned int length;
1087     char c;
1088     Tcl_Interp *interp = htPtr->interp;
1089 
1090     length = strlen(string);
1091     c = string[0];
1092 
1093     if ((c == 'a') && (strncmp(string, "anchor", length) == 0)) {
1094 	*indexPtr = htPtr->selAnchor;
1095     } else if ((c == 's') && (length > 4)) {
1096 	if (strncmp(string, "sel.first", length) == 0) {
1097 	    *indexPtr = htPtr->selFirst;
1098 	} else if (strncmp(string, "sel.last", length) == 0) {
1099 	    *indexPtr = htPtr->selLast;
1100 	} else {
1101 	    goto badIndex;	/* Not a valid index */
1102 	}
1103 	if (*indexPtr < 0) {
1104 	    Tcl_AppendResult(interp, "bad index \"", string,
1105 		"\": nothing selected in \"",
1106 		Tk_PathName(htPtr->tkwin), "\"", (char *)NULL);
1107 	    return TCL_ERROR;
1108 	}
1109     } else if ((c == 'p') && (length > 5) &&
1110 	(strncmp(string, "page.top", length) == 0)) {
1111 	int first;
1112 
1113 	first = htPtr->first;
1114 	if (first < 0) {
1115 	    first = 0;
1116 	}
1117 	*indexPtr = htPtr->lineArr[first].textStart;
1118     } else if ((c == 'p') && (length > 5) &&
1119 	(strncmp(string, "page.bottom", length) == 0)) {
1120 	*indexPtr = htPtr->lineArr[htPtr->last].textEnd;
1121     } else if (c == '@') {	/* Screen position */
1122 	if (GetXYPosIndex(htPtr, string, indexPtr) != TCL_OK) {
1123 	    return TCL_ERROR;
1124 	}
1125     } else {
1126 	char *period;
1127 
1128 	period = strchr(string, '.');
1129 	if (period == NULL) {	/* Raw index */
1130 	    int tindex;
1131 
1132 	    if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
1133 		tindex = htPtr->nChars - 1;
1134 	    } else if (Tcl_GetInt(interp, string, &tindex) != TCL_OK) {
1135 		goto badIndex;
1136 	    }
1137 	    if (tindex < 0) {
1138 		tindex = 0;
1139 	    } else if (tindex > (htPtr->nChars - 1)) {
1140 		tindex = htPtr->nChars - 1;
1141 	    }
1142 	    *indexPtr = tindex;
1143 	} else {
1144 	    int lindex, cindex, offset;
1145 	    Line *linePtr;
1146 	    int result;
1147 
1148 	    *period = '\0';
1149 	    result = TCL_OK;
1150 	    if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
1151 		lindex = htPtr->nLines - 1;
1152 	    } else {
1153 		result = Tcl_GetInt(interp, string, &lindex);
1154 	    }
1155 	    *period = '.';	/* Repair index string before returning */
1156 	    if (result != TCL_OK) {
1157 		goto badIndex;	/* Bad line number */
1158 	    }
1159 	    if (lindex < 0) {
1160 		lindex = 0;	/* Silently repair bad line numbers */
1161 	    }
1162 	    if (htPtr->nChars == 0) {
1163 		*indexPtr = 0;
1164 		return TCL_OK;
1165 	    }
1166 	    if (lindex >= htPtr->nLines) {
1167 		lindex = htPtr->nLines - 1;
1168 	    }
1169 	    linePtr = htPtr->lineArr + lindex;
1170 	    cindex = 0;
1171 	    if ((*(period + 1) != '\0')) {
1172 		string = period + 1;
1173 		if ((string[0] == 'e') && (strcmp(string, "end") == 0)) {
1174 		    cindex = linePtr->textEnd - linePtr->textStart;
1175 		} else if (Tcl_GetInt(interp, string, &cindex) != TCL_OK) {
1176 		    goto badIndex;
1177 		}
1178 	    }
1179 	    if (cindex < 0) {
1180 		cindex = 0;	/* Silently fix bogus indices */
1181 	    }
1182 	    offset = 0;
1183 	    if (htPtr->nChars > 0) {
1184 		offset = linePtr->textStart + cindex;
1185 		if (offset > linePtr->textEnd) {
1186 		    offset = linePtr->textEnd;
1187 		}
1188 	    }
1189 	    *indexPtr = offset;
1190 	}
1191     }
1192     if (htPtr->nChars == 0) {
1193 	*indexPtr = 0;
1194     }
1195     return TCL_OK;
1196 
1197   badIndex:
1198 
1199     /*
1200      * Some of the paths here leave messages in interp->result, so we
1201      * have to clear it out before storing our own message.
1202      */
1203     Tcl_ResetResult(interp);
1204     Tcl_AppendResult(interp, "bad index \"", string, "\": \
1205 should be one of the following: anchor, sel.first, sel.last, page.bottom, \
1206 page.top, @x,y, index, line.char", (char *)NULL);
1207     return TCL_ERROR;
1208 }
1209 
1210 /*
1211  *--------------------------------------------------------------
1212  *
1213  * GetIndex --
1214  *
1215  *	Get the index from a string representing a text index.
1216  *
1217  *
1218  * Results:
1219  *	A standard Tcl result.  If "string" is a valid index, then
1220  *	*indexPtr is filled with the numeric index corresponding.
1221  *	Otherwise an error message is left in interp->result.
1222  *
1223  * Side effects:
1224  *	None.
1225  *
1226  *--------------------------------------------------------------
1227  */
1228 static int
GetIndex(htPtr,string,indexPtr)1229 GetIndex(htPtr, string, indexPtr)
1230     HText *htPtr;		/* Text for which the index is being
1231 				 * specified. */
1232     char *string;		/* Numerical index into htPtr's element
1233 				 * list, or "end" to refer to last element. */
1234     int *indexPtr;		/* Where to store converted relief. */
1235 {
1236     int tindex;
1237 
1238     if (ParseIndex(htPtr, string, &tindex) != TCL_OK) {
1239 	return TCL_ERROR;
1240     }
1241     *indexPtr = tindex;
1242     return TCL_OK;
1243 }
1244 
1245 /*
1246  * ----------------------------------------------------------------------
1247  *
1248  * GetTextPosition --
1249  *
1250  * 	Performs a binary search for the index located on line in
1251  *	the text. The search is limited to those lines between
1252  *	low and high inclusive.
1253  *
1254  * Results:
1255  *	Returns the line number at the given Y coordinate. If position
1256  *	does not correspond to any of the lines in the given the set,
1257  *	-1 is returned.
1258  *
1259  * ----------------------------------------------------------------------
1260  */
1261 static int
GetTextPosition(htPtr,tindex,lindexPtr,cindexPtr)1262 GetTextPosition(htPtr, tindex, lindexPtr, cindexPtr)
1263     HText *htPtr;
1264     int tindex;
1265     int *lindexPtr;
1266     int *cindexPtr;
1267 {
1268     int lindex, cindex;
1269 
1270     lindex = cindex = 0;
1271     if (htPtr->nChars > 0) {
1272 	Line *linePtr;
1273 
1274 	lindex = IndexSearch(htPtr, tindex, 0, htPtr->nLines - 1);
1275 	if (lindex < 0) {
1276 	    char string[200];
1277 
1278 	    sprintf(string, "can't determine line number from index \"%d\"",
1279 		tindex);
1280 	    Tcl_AppendResult(htPtr->interp, string, (char *)NULL);
1281 	    return TCL_ERROR;
1282 	}
1283 	linePtr = htPtr->lineArr + lindex;
1284 	if (tindex > linePtr->textEnd) {
1285 	    tindex = linePtr->textEnd;
1286 	}
1287 	cindex = tindex - linePtr->textStart;
1288     }
1289     *lindexPtr = lindex;
1290     *cindexPtr = cindex;
1291     return TCL_OK;
1292 }
1293 
1294 /* EmbeddedWidget Procedures */
1295 /*
1296  *----------------------------------------------------------------------
1297  *
1298  * GetEmbeddedWidgetWidth --
1299  *
1300  *	Returns the width requested by the embedded widget. The requested
1301  *	space also includes any internal padding which has been designated
1302  *	for this window.
1303  *
1304  * Results:
1305  *	Returns the requested width of the embedded widget.
1306  *
1307  *----------------------------------------------------------------------
1308  */
1309 static int
GetEmbeddedWidgetWidth(winPtr)1310 GetEmbeddedWidgetWidth(winPtr)
1311     EmbeddedWidget *winPtr;
1312 {
1313     int width;
1314 
1315     if (winPtr->reqWidth > 0) {
1316 	width = winPtr->reqWidth;
1317     } else if (winPtr->relWidth > 0.0) {
1318 	width = (int)
1319 	    ((double)Tk_Width(winPtr->htPtr->tkwin) * winPtr->relWidth + 0.5);
1320     } else {
1321 	width = Tk_ReqWidth(winPtr->tkwin);
1322     }
1323     width += (2 * winPtr->ipadX);
1324     return width;
1325 }
1326 
1327 /*
1328  *----------------------------------------------------------------------
1329  *
1330  * GetEmbeddedWidgetHeight --
1331  *
1332  *	Returns the height requested by the embedded widget. The requested
1333  *	space also includes any internal padding which has been designated
1334  *	for this window.
1335  *
1336  * Results:
1337  *	Returns the requested height of the embedded widget.
1338  *
1339  *----------------------------------------------------------------------
1340  */
1341 static int
GetEmbeddedWidgetHeight(winPtr)1342 GetEmbeddedWidgetHeight(winPtr)
1343     EmbeddedWidget *winPtr;
1344 {
1345     int height;
1346 
1347     if (winPtr->reqHeight > 0) {
1348 	height = winPtr->reqHeight;
1349     } else if (winPtr->relHeight > 0.0) {
1350 	height = (int)((double)Tk_Height(winPtr->htPtr->tkwin) *
1351 	    winPtr->relHeight + 0.5);
1352     } else {
1353 	height = Tk_ReqHeight(winPtr->tkwin);
1354     }
1355     height += (2 * winPtr->ipadY);
1356     return height;
1357 }
1358 
1359 /*
1360  * --------------------------------------------------------------
1361  *
1362  * EmbeddedWidgetEventProc --
1363  *
1364  * 	This procedure is invoked by the Tk dispatcher for various
1365  * 	events on hypertext widgets.
1366  *
1367  * Results:
1368  *	None.
1369  *
1370  * Side effects:
1371  *	When the window gets deleted, internal structures get
1372  *	cleaned up.  When it gets exposed, it is redisplayed.
1373  *
1374  * --------------------------------------------------------------
1375  */
1376 static void
EmbeddedWidgetEventProc(clientData,eventPtr)1377 EmbeddedWidgetEventProc(clientData, eventPtr)
1378     ClientData clientData;	/* Information about the embedded widget. */
1379     XEvent *eventPtr;		/* Information about event. */
1380 {
1381     EmbeddedWidget *winPtr = clientData;
1382     HText *htPtr;
1383 
1384     if ((winPtr == NULL) || (winPtr->tkwin == NULL)) {
1385 	return;
1386     }
1387     htPtr = winPtr->htPtr;
1388 
1389     if (eventPtr->type == DestroyNotify) {
1390 	Blt_HashEntry *hPtr;
1391 	/*
1392 	 * Mark the widget as deleted by dereferencing the Tk window
1393 	 * pointer.  Zero out the height and width to collapse the area
1394 	 * used by the widget.  Redraw the window only if the widget is
1395 	 * currently visible.
1396 	 */
1397 	winPtr->htPtr->flags |= REQUEST_LAYOUT;
1398 	if (Tk_IsMapped(winPtr->tkwin) && (winPtr->flags & WIDGET_VISIBLE)) {
1399 	    EventuallyRedraw(htPtr);
1400 	}
1401 	Tk_DeleteEventHandler(winPtr->tkwin, StructureNotifyMask,
1402 	    EmbeddedWidgetEventProc, winPtr);
1403 	hPtr = Blt_FindHashEntry(&(htPtr->widgetTable), (char *)winPtr->tkwin);
1404 	Blt_DeleteHashEntry(&(htPtr->widgetTable), hPtr);
1405 	winPtr->cavityWidth = winPtr->cavityHeight = 0;
1406 	winPtr->tkwin = NULL;
1407 
1408     } else if (eventPtr->type == ConfigureNotify) {
1409 	/*
1410 	 * EmbeddedWidgets can't request new positions. Worry only about resizing.
1411 	 */
1412 	if (winPtr->winWidth != Tk_Width(winPtr->tkwin) ||
1413 	    winPtr->winHeight != Tk_Height(winPtr->tkwin)) {
1414 	    EventuallyRedraw(htPtr);
1415 	    htPtr->flags |= REQUEST_LAYOUT;
1416 	}
1417     }
1418 }
1419 
1420 /*
1421  *--------------------------------------------------------------
1422  *
1423  * EmbeddedWidgetCustodyProc --
1424  *
1425  *	This procedure is invoked when a embedded widget has been
1426  *	stolen by another geometry manager.  The information and
1427  *	memory associated with the embedded widget is released.
1428  *
1429  * Results:
1430  *	None.
1431  *
1432  * Side effects:
1433  *	Arranges for the widget formerly associated with the widget
1434  *	to have its layout re-computed and arranged at the
1435  *	next idle point.
1436  *
1437  *--------------------------------------------------------------
1438  */
1439  /* ARGSUSED */
1440 static void
EmbeddedWidgetCustodyProc(clientData,tkwin)1441 EmbeddedWidgetCustodyProc(clientData, tkwin)
1442     ClientData clientData;	/* Information about the former embedded widget. */
1443     Tk_Window tkwin;		/* Not used. */
1444 {
1445     Blt_HashEntry *hPtr;
1446     EmbeddedWidget *winPtr = clientData;
1447     /*
1448      * Mark the widget as deleted by dereferencing the Tk window
1449      * pointer.  Zero out the height and width to collapse the area
1450      * used by the widget.  Redraw the window only if the widget is
1451      * currently visible.
1452      */
1453     winPtr->htPtr->flags |= REQUEST_LAYOUT;
1454     if (Tk_IsMapped(winPtr->tkwin) && (winPtr->flags & WIDGET_VISIBLE)) {
1455 	EventuallyRedraw(winPtr->htPtr);
1456     }
1457     Tk_DeleteEventHandler(winPtr->tkwin, StructureNotifyMask,
1458 	EmbeddedWidgetEventProc, winPtr);
1459     hPtr = Blt_FindHashEntry(&(winPtr->htPtr->widgetTable),
1460 			     (char *)winPtr->tkwin);
1461     Blt_DeleteHashEntry(&(winPtr->htPtr->widgetTable), hPtr);
1462     winPtr->cavityWidth = winPtr->cavityHeight = 0;
1463     winPtr->tkwin = NULL;
1464 }
1465 
1466 /*
1467  *--------------------------------------------------------------
1468  *
1469  * EmbeddedWidgetGeometryProc --
1470  *
1471  *	This procedure is invoked by Tk_GeometryRequest for
1472  *	embedded widgets managed by the hypertext widget.
1473  *
1474  * Results:
1475  *	None.
1476  *
1477  * Side effects:
1478  *	Arranges for tkwin, and all its managed siblings, to
1479  *	be repacked and drawn at the next idle point.
1480  *
1481  *--------------------------------------------------------------
1482  */
1483  /* ARGSUSED */
1484 static void
EmbeddedWidgetGeometryProc(clientData,tkwin)1485 EmbeddedWidgetGeometryProc(clientData, tkwin)
1486     ClientData clientData;	/* Information about window that got new
1487 			         * preferred geometry.  */
1488     Tk_Window tkwin;		/* Other Tk-related information about the
1489 			         * window. */
1490 {
1491     EmbeddedWidget *winPtr = clientData;
1492 
1493     winPtr->htPtr->flags |= REQUEST_LAYOUT;
1494     EventuallyRedraw(winPtr->htPtr);
1495 }
1496 
1497 /*
1498  * ----------------------------------------------------------------------
1499  *
1500  * FindEmbeddedWidget --
1501  *
1502  *	Searches for a widget matching the path name given
1503  *	If found, the pointer to the widget structure is returned,
1504  *	otherwise NULL.
1505  *
1506  * Results:
1507  *	The pointer to the widget structure. If not found, NULL.
1508  *
1509  * ----------------------------------------------------------------------
1510  */
1511 static EmbeddedWidget *
FindEmbeddedWidget(htPtr,tkwin)1512 FindEmbeddedWidget(htPtr, tkwin)
1513     HText *htPtr;		/* Hypertext widget structure */
1514     Tk_Window tkwin;		/* Path name of embedded widget  */
1515 {
1516     Blt_HashEntry *hPtr;
1517 
1518     hPtr = Blt_FindHashEntry(&(htPtr->widgetTable), (char *)tkwin);
1519     if (hPtr != NULL) {
1520 	return (EmbeddedWidget *) Blt_GetHashValue(hPtr);
1521     }
1522     return NULL;
1523 }
1524 
1525 /*
1526  * ----------------------------------------------------------------------
1527  *
1528  * CreateEmbeddedWidget --
1529  *
1530  * 	This procedure creates and initializes a new embedded widget
1531  *	in the hyper text widget.
1532  *
1533  * Results:
1534  *	The return value is a pointer to a structure describing the
1535  *	new embedded widget.  If an error occurred, then the return
1536  *	value is NULL and an error message is left in interp->result.
1537  *
1538  * Side effects:
1539  *	Memory is allocated. EmbeddedWidget window is mapped.
1540  *	Callbacks are set up for embedded widget resizes and geometry
1541  *	requests.
1542  *
1543  * ----------------------------------------------------------------------
1544  */
1545 static EmbeddedWidget *
CreateEmbeddedWidget(htPtr,name)1546 CreateEmbeddedWidget(htPtr, name)
1547     HText *htPtr;		/* Hypertext widget */
1548     char *name;			/* Name of embedded widget */
1549 {
1550     EmbeddedWidget *winPtr;
1551     Tk_Window tkwin;
1552     Blt_HashEntry *hPtr;
1553     int isNew;
1554 
1555     tkwin = Tk_NameToWindow(htPtr->interp, name, htPtr->tkwin);
1556     if (tkwin == NULL) {
1557 	return NULL;
1558     }
1559     if (Tk_Parent(tkwin) != htPtr->tkwin) {
1560 	Tcl_AppendResult(htPtr->interp, "parent window of \"", name,
1561 	    "\" must be \"", Tk_PathName(htPtr->tkwin), "\"", (char *)NULL);
1562 	return NULL;
1563     }
1564     hPtr = Blt_CreateHashEntry(&(htPtr->widgetTable), (char *)tkwin, &isNew);
1565     /* Check is the widget is already embedded into this widget */
1566     if (!isNew) {
1567 	Tcl_AppendResult(htPtr->interp, "\"", name,
1568 	    "\" is already appended to ", Tk_PathName(htPtr->tkwin),
1569 	    (char *)NULL);
1570 	return NULL;
1571     }
1572     winPtr = Blt_Calloc(1, sizeof(EmbeddedWidget));
1573     assert(winPtr);
1574     winPtr->flags = 0;
1575     winPtr->tkwin = tkwin;
1576     winPtr->htPtr = htPtr;
1577     winPtr->x = winPtr->y = 0;
1578     winPtr->fill = FILL_NONE;
1579     winPtr->justify = JUSTIFY_CENTER;
1580     winPtr->anchor = TK_ANCHOR_CENTER;
1581     Blt_SetHashValue(hPtr, winPtr);
1582 
1583     Tk_ManageGeometry(tkwin, &htextMgrInfo, winPtr);
1584     Tk_CreateEventHandler(tkwin, StructureNotifyMask, EmbeddedWidgetEventProc,
1585 	  winPtr);
1586     return winPtr;
1587 }
1588 
1589 /*
1590  * ----------------------------------------------------------------------
1591  *
1592  * DestroyEmbeddedWidget --
1593  *
1594  * 	This procedure is invoked by DestroyLine to clean up the
1595  * 	internal structure of a widget.
1596  *
1597  * Results:
1598  *	None.
1599  *
1600  * Side effects:
1601  *	Everything associated with the widget is freed up.
1602  *
1603  * ----------------------------------------------------------------------
1604  */
1605 static void
DestroyEmbeddedWidget(winPtr)1606 DestroyEmbeddedWidget(winPtr)
1607     EmbeddedWidget *winPtr;
1608 {
1609     /* Destroy the embedded widget if it still exists */
1610     if (winPtr->tkwin != NULL) {
1611 	Blt_HashEntry *hPtr;
1612 
1613 	Tk_DeleteEventHandler(winPtr->tkwin, StructureNotifyMask,
1614 	    EmbeddedWidgetEventProc, winPtr);
1615 	hPtr = Blt_FindHashEntry(&(winPtr->htPtr->widgetTable),
1616 	    (char *)winPtr->tkwin);
1617 	Blt_DeleteHashEntry(&(winPtr->htPtr->widgetTable), hPtr);
1618 	Tk_DestroyWindow(winPtr->tkwin);
1619     }
1620     Blt_Free(winPtr);
1621 }
1622 
1623 /* Line Procedures */
1624 /*
1625  * ----------------------------------------------------------------------
1626  *
1627  * CreateLine --
1628  *
1629  * 	This procedure creates and initializes a new line of text.
1630  *
1631  * Results:
1632  *	The return value is a pointer to a structure describing the new
1633  * 	line of text.  If an error occurred, then the return value is NULL
1634  *	and an error message is left in interp->result.
1635  *
1636  * Side effects:
1637  *	Memory is allocated.
1638  *
1639  * ----------------------------------------------------------------------
1640  */
1641 static Line *
CreateLine(htPtr)1642 CreateLine(htPtr)
1643     HText *htPtr;
1644 {
1645     Line *linePtr;
1646 
1647     if (htPtr->nLines >= htPtr->arraySize) {
1648 	if (htPtr->arraySize == 0) {
1649 	    htPtr->arraySize = DEF_LINES_ALLOC;
1650 	} else {
1651 	    htPtr->arraySize += htPtr->arraySize;
1652 	}
1653 	if (ResizeArray((char **)&(htPtr->lineArr), sizeof(Line),
1654 		htPtr->arraySize, htPtr->nLines) != TCL_OK) {
1655 	    return NULL;
1656 	}
1657     }
1658     /* Initialize values in the new entry */
1659 
1660     linePtr = htPtr->lineArr + htPtr->nLines;
1661     linePtr->offset = 0;
1662     linePtr->height = linePtr->width = 0;
1663     linePtr->textStart = 0;
1664     linePtr->textEnd = -1;
1665     linePtr->baseline = 0;
1666     linePtr->chainPtr = Blt_ChainCreate();
1667 
1668     htPtr->nLines++;
1669     return linePtr;
1670 }
1671 
1672 /*
1673  * ----------------------------------------------------------------------
1674  *
1675  * DestroyLine --
1676  *
1677  * 	This procedure is invoked to clean up the internal structure
1678  *	of a line.
1679  *
1680  * Results:
1681  *	None.
1682  *
1683  * Side effects:
1684  *	Everything associated with the line (text and widgets) is
1685  *	freed up.
1686  *
1687  * ----------------------------------------------------------------------
1688  */
1689 static void
DestroyLine(linePtr)1690 DestroyLine(linePtr)
1691     Line *linePtr;
1692 {
1693     Blt_ChainLink *linkPtr;
1694     EmbeddedWidget *winPtr;
1695 
1696     /* Free the list of embedded widget structures */
1697     for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
1698 	linkPtr = Blt_ChainNextLink(linkPtr)) {
1699 	winPtr = Blt_ChainGetValue(linkPtr);
1700 	DestroyEmbeddedWidget(winPtr);
1701     }
1702     Blt_ChainDestroy(linePtr->chainPtr);
1703 }
1704 
1705 static void
FreeText(htPtr)1706 FreeText(htPtr)
1707     HText *htPtr;
1708 {
1709     int i;
1710 
1711     for (i = 0; i < htPtr->nLines; i++) {
1712 	DestroyLine(htPtr->lineArr + i);
1713     }
1714     htPtr->nLines = 0;
1715     htPtr->nChars = 0;
1716     if (htPtr->charArr != NULL) {
1717 	Blt_Free(htPtr->charArr);
1718 	htPtr->charArr = NULL;
1719     }
1720 }
1721 
1722 /* Text Procedures */
1723 /*
1724  * ----------------------------------------------------------------------
1725  *
1726  * DestroyText --
1727  *
1728  * 	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
1729  *	to clean up the internal structure of a HText at a safe time
1730  *	(when no-one is using it anymore).
1731  *
1732  * Results:
1733  *	None.
1734  *
1735  * Side effects:
1736  *	Everything associated with the widget is freed up.
1737  *
1738  * ----------------------------------------------------------------------
1739  */
1740 static void
DestroyText(dataPtr)1741 DestroyText(dataPtr)
1742     DestroyData dataPtr;	/* Info about hypertext widget. */
1743 {
1744     HText *htPtr = (HText *)dataPtr;
1745 
1746     Tk_FreeOptions(configSpecs, (char *)htPtr, htPtr->display, 0);
1747     if (htPtr->drawGC != NULL) {
1748 	Tk_FreeGC(htPtr->display, htPtr->drawGC);
1749     }
1750     if (htPtr->fillGC != NULL) {
1751 	Tk_FreeGC(htPtr->display, htPtr->fillGC);
1752     }
1753     if (htPtr->tile != NULL) {
1754 	Blt_FreeTile(htPtr->tile);
1755     }
1756     if (htPtr->selectGC != NULL) {
1757 	Tk_FreeGC(htPtr->display, htPtr->selectGC);
1758     }
1759     FreeText(htPtr);
1760     if (htPtr->lineArr != NULL) {
1761 	Blt_Free(htPtr->lineArr);
1762     }
1763     Blt_DeleteHashTable(&(htPtr->widgetTable));
1764     Blt_Free(htPtr);
1765 }
1766 
1767 /*
1768  * --------------------------------------------------------------
1769  *
1770  * TextEventProc --
1771  *
1772  * 	This procedure is invoked by the Tk dispatcher for various
1773  * 	events on hypertext widgets.
1774  *
1775  * Results:
1776  *	None.
1777  *
1778  * Side effects:
1779  *	When the window gets deleted, internal structures get
1780  *	cleaned up.  When it gets exposed, it is redisplayed.
1781  *
1782  * --------------------------------------------------------------
1783  */
1784 static void
TextEventProc(clientData,eventPtr)1785 TextEventProc(clientData, eventPtr)
1786     ClientData clientData;	/* Information about window. */
1787     XEvent *eventPtr;		/* Information about event. */
1788 {
1789     HText *htPtr = clientData;
1790 
1791     if (eventPtr->type == ConfigureNotify) {
1792 	if ((htPtr->lastWidth != Tk_Width(htPtr->tkwin)) ||
1793 	    (htPtr->lastHeight != Tk_Height(htPtr->tkwin))) {
1794 	    htPtr->flags |= (REQUEST_LAYOUT | TEXT_DIRTY);
1795 	    EventuallyRedraw(htPtr);
1796 	}
1797     } else if (eventPtr->type == Expose) {
1798 
1799 	/*
1800 	 * If the Expose event was synthetic (i.e. we manufactured it
1801 	 * ourselves during a redraw operation), toggle the bit flag
1802 	 * which controls redraws.
1803 	 */
1804 
1805 	if (eventPtr->xexpose.send_event) {
1806 	    htPtr->flags ^= IGNORE_EXPOSURES;
1807 	    return;
1808 	}
1809 	if ((eventPtr->xexpose.count == 0) &&
1810 	    !(htPtr->flags & IGNORE_EXPOSURES)) {
1811 	    htPtr->flags |= TEXT_DIRTY;
1812 	    EventuallyRedraw(htPtr);
1813 	}
1814     } else if (eventPtr->type == DestroyNotify) {
1815 	if (htPtr->tkwin != NULL) {
1816 	    htPtr->tkwin = NULL;
1817 	    Tcl_DeleteCommandFromToken(htPtr->interp, htPtr->cmdToken);
1818 	}
1819 	if (htPtr->flags & REDRAW_PENDING) {
1820 	    Tcl_CancelIdleCall(DisplayText, htPtr);
1821 	}
1822 	Tcl_EventuallyFree(htPtr, DestroyText);
1823     }
1824 }
1825 
1826 /*
1827  *----------------------------------------------------------------------
1828  *
1829  * TextDeleteCmdProc --
1830  *
1831  *	This procedure is invoked when a widget command is deleted.  If
1832  *	the widget isn't already in the process of being destroyed,
1833  *	this command destroys it.
1834  *
1835  * Results:
1836  *	None.
1837  *
1838  * Side effects:
1839  *	The widget is destroyed.
1840  *
1841  *----------------------------------------------------------------------
1842  */
1843 
1844 static void
TextDeleteCmdProc(clientData)1845 TextDeleteCmdProc(clientData)
1846     ClientData clientData;	/* Pointer to widget record for widget. */
1847 {
1848     HText *htPtr = clientData;
1849 
1850     /*
1851      * This procedure could be invoked either because the window was
1852      * destroyed and the command was then deleted (in which case tkwin
1853      * is NULL) or because the command was deleted, and then this procedure
1854      * destroys the widget.
1855      */
1856 
1857     if (htPtr->tkwin != NULL) {
1858 	Tk_Window tkwin;
1859 
1860 	tkwin = htPtr->tkwin;
1861 	htPtr->tkwin = NULL;
1862 	Tk_DestroyWindow(tkwin);
1863 #ifdef ITCL_NAMESPACES
1864 	Itk_SetWidgetCommand(tkwin, (Tcl_Command) NULL);
1865 #endif /* ITCL_NAMESPACES */
1866     }
1867 }
1868 
1869 /*
1870  *----------------------------------------------------------------------
1871  *
1872  * TileChangedProc
1873  *
1874  *	Stub for image change notifications.  Since we immediately draw
1875  *	the image into a pixmap, we don't care about image changes.
1876  *
1877  *	It would be better if Tk checked for NULL proc pointers.
1878  *
1879  * Results:
1880  *	None.
1881  *
1882  *----------------------------------------------------------------------
1883  */
1884 /*ARGSUSED*/
1885 static void
TileChangedProc(clientData,tile)1886 TileChangedProc(clientData, tile)
1887     ClientData clientData;
1888     Blt_Tile tile;		/* Not used. */
1889 {
1890     HText *htPtr = clientData;
1891 
1892     if (htPtr->tkwin != NULL) {
1893 	EventuallyRedraw(htPtr);
1894     }
1895 }
1896 
1897 /* Configuration Procedures */
1898 static void
ResetTextInfo(htPtr)1899 ResetTextInfo(htPtr)
1900     HText *htPtr;
1901 {
1902     htPtr->first = 0;
1903     htPtr->last = htPtr->nLines - 1;
1904     htPtr->selFirst = htPtr->selLast = -1;
1905     htPtr->selAnchor = 0;
1906     htPtr->pendingX = htPtr->pendingY = 0;
1907     htPtr->worldWidth = htPtr->worldHeight = 0;
1908     htPtr->xOffset = htPtr->yOffset = 0;
1909 }
1910 
1911 static Line *
GetLastLine(htPtr)1912 GetLastLine(htPtr)
1913     HText *htPtr;
1914 {
1915     if (htPtr->nLines == 0) {
1916 	return CreateLine(htPtr);
1917     }
1918     return (htPtr->lineArr + (htPtr->nLines - 1));
1919 }
1920 
1921 /*
1922  * ----------------------------------------------------------------------
1923  *
1924  * ReadNamedFile --
1925  *
1926  * 	Read the named file into a newly allocated buffer.
1927  *
1928  * Results:
1929  *	Returns the size of the allocated buffer if the file was
1930  *	read correctly.  Otherwise -1 is returned and "interp->result"
1931  *	will contain an error message.
1932  *
1933  * Side Effects:
1934  *	If successful, the contents of "bufferPtr" will point
1935  *	to the allocated buffer.
1936  *
1937  * ----------------------------------------------------------------------
1938  */
1939 static int
ReadNamedFile(interp,fileName,bufferPtr)1940 ReadNamedFile(interp, fileName, bufferPtr)
1941     Tcl_Interp *interp;
1942     char *fileName;
1943     char **bufferPtr;
1944 {
1945     FILE *f;
1946     int nRead, fileSize;
1947     int count, bytesLeft;
1948     char *buffer;
1949 #if defined(_MSC_VER) || defined(__BORLANDC__)
1950 #define fstat	 _fstat
1951 #define stat	 _stat
1952 #ifdef _MSC_VER
1953 #define fileno	 _fileno
1954 #endif
1955 #endif /* _MSC_VER || __BORLANDC__ */
1956 
1957     struct stat fileInfo;
1958 
1959     f = fopen(fileName, "r");
1960     if (f == NULL) {
1961 	Tcl_AppendResult(interp, "can't open \"", fileName,
1962 	    "\" for reading: ", Tcl_PosixError(interp), (char *)NULL);
1963 	return -1;
1964     }
1965     if (fstat(fileno(f), &fileInfo) < 0) {
1966 	Tcl_AppendResult(interp, "can't stat \"", fileName, "\": ",
1967 	    Tcl_PosixError(interp), (char *)NULL);
1968 	fclose(f);
1969 	return -1;
1970     }
1971     fileSize = fileInfo.st_size + 1;
1972     buffer = Blt_Malloc(sizeof(char) * fileSize);
1973     if (buffer == NULL) {
1974 	fclose(f);
1975 	return -1;		/* Can't allocate memory for file buffer */
1976     }
1977     count = 0;
1978     for (bytesLeft = fileInfo.st_size; bytesLeft > 0; bytesLeft -= nRead) {
1979 	nRead = fread(buffer + count, sizeof(char), bytesLeft, f);
1980 	if (nRead < 0) {
1981 	    Tcl_AppendResult(interp, "error reading \"", fileName, "\": ",
1982 		Tcl_PosixError(interp), (char *)NULL);
1983 	    fclose(f);
1984 	    Blt_Free(buffer);
1985 	    return -1;
1986 	} else if (nRead == 0) {
1987 	    break;
1988 	}
1989 	count += nRead;
1990     }
1991     fclose(f);
1992     buffer[count] = '\0';
1993     *bufferPtr = buffer;
1994     return count;
1995 }
1996 
1997 /*
1998  * ----------------------------------------------------------------------
1999  *
2000  * CollectCommand --
2001  *
2002  * 	Collect the characters representing a Tcl command into a
2003  *	given buffer.
2004  *
2005  * Results:
2006  *	Returns the number of bytes examined.  If an error occurred,
2007  *	-1 is returned and "interp->result" will contain an error
2008  *	message.
2009  *
2010  * Side Effects:
2011  *	If successful, the "cmdArr" will be filled with the string
2012  *	representing the Tcl command.
2013  *
2014  * ----------------------------------------------------------------------
2015  */
2016 
2017 static int
CollectCommand(htPtr,inputArr,maxBytes,cmdArr)2018 CollectCommand(htPtr, inputArr, maxBytes, cmdArr)
2019     HText *htPtr;		/* Widget record */
2020     char inputArr[];		/* Array of bytes representing the htext input */
2021     int maxBytes;		/* Maximum number of bytes left in input */
2022     char cmdArr[];		/* Output buffer to be filled with the Tcl
2023 				 * command */
2024 {
2025     int c;
2026     int i;
2027     int state, count;
2028 
2029     /* Simply collect the all the characters until %% into a buffer */
2030 
2031     state = count = 0;
2032     for (i = 0; i < maxBytes; i++) {
2033 	c = inputArr[i];
2034 	if (c == htPtr->specChar) {
2035 	    state++;
2036 	} else if ((state == 0) && (c == '\\')) {
2037 	    state = 3;
2038 	} else {
2039 	    state = 0;
2040 	}
2041 	switch (state) {
2042 	case 2:		/* End of command block found */
2043 	    cmdArr[count - 1] = '\0';
2044 	    return i;
2045 
2046 	case 4:		/* Escaped block designator */
2047 	    cmdArr[count] = c;
2048 	    state = 0;
2049 	    break;
2050 
2051 	default:		/* Add to command buffer */
2052 	    cmdArr[count++] = c;
2053 	    break;
2054 	}
2055     }
2056     Tcl_AppendResult(htPtr->interp, "premature end of TCL command block",
2057 	(char *)NULL);
2058     return -1;
2059 }
2060 
2061 /*
2062  * ----------------------------------------------------------------------
2063  *
2064  * ParseInput --
2065  *
2066  * 	Parse the input to the HText structure into an array of lines.
2067  *	Each entry contains the beginning index and end index of the
2068  *	characters in the text array which comprise the line.
2069  *
2070  *	|*|*|*|\n|T|h|i|s| |a| |l|i|n|e| |o|f| |t|e|x|t|.|\n|*|*|*|
2071  *                ^					  ^
2072  *	          textStart				  textEnd
2073  *
2074  *	Note that the end index contains the '\n'.
2075  *
2076  * Results:
2077  *	Returns TCL_OK or error depending if the file was read correctly.
2078  *
2079  * ----------------------------------------------------------------------
2080  */
2081 static int
ParseInput(interp,htPtr,input,nBytes)2082 ParseInput(interp, htPtr, input, nBytes)
2083     Tcl_Interp *interp;
2084     HText *htPtr;
2085     char input[];
2086     int nBytes;
2087 {
2088     int c;
2089     int i;
2090     char *textArr;
2091     char *cmdArr;
2092     int count, nLines;
2093     int length;
2094     int state;
2095     Line *linePtr;
2096 
2097     linePtr = CreateLine(htPtr);
2098     if (linePtr == NULL) {
2099 	return TCL_ERROR;	/* Error allocating the line structure */
2100     }
2101     /*  Right now, we replace the text array instead of appending to it */
2102 
2103     linePtr->textStart = 0;
2104 
2105     /* In the worst case, assume the entire input could be Tcl commands */
2106     cmdArr = Blt_Malloc(sizeof(char) * (nBytes + 1));
2107 
2108     textArr = Blt_Malloc(sizeof(char) * (nBytes + 1));
2109     if (htPtr->charArr != NULL) {
2110 	Blt_Free(htPtr->charArr);
2111     }
2112     htPtr->charArr = textArr;
2113     htPtr->nChars = 0;
2114 
2115     nLines = count = state = 0;
2116     htPtr->flags &= ~WIDGET_APPENDED;
2117 
2118     for (i = 0; i < nBytes; i++) {
2119 	c = input[i];
2120 	if (c == htPtr->specChar) {
2121 	    state++;
2122 	} else if (c == '\n') {
2123 	    state = -1;
2124 	} else if ((state == 0) && (c == '\\')) {
2125 	    state = 3;
2126 	} else {
2127 	    state = 0;
2128 	}
2129 	switch (state) {
2130 	case 2:		/* Block of Tcl commands found */
2131 	    count--, i++;
2132 	    length = CollectCommand(htPtr, input + i, nBytes - i, cmdArr);
2133 	    if (length < 0) {
2134 		goto error;
2135 	    }
2136 	    i += length;
2137 	    linePtr->textEnd = count;
2138 	    htPtr->nChars = count + 1;
2139 	    if (Tcl_Eval(interp, cmdArr) != TCL_OK) {
2140 		goto error;
2141 	    }
2142 	    if (htPtr->flags & WIDGET_APPENDED) {
2143 		/* Indicates the location a embedded widget in the text array */
2144 		textArr[count++] = ' ';
2145 		htPtr->flags &= ~WIDGET_APPENDED;
2146 	    }
2147 	    state = 0;
2148 	    break;
2149 
2150 	case 4:		/* Escaped block designator */
2151 	    textArr[count - 1] = c;
2152 	    state = 0;
2153 	    break;
2154 
2155 	case -1:		/* End of line or input */
2156 	    linePtr->textEnd = count;
2157 	    textArr[count++] = '\n';
2158 	    nLines++;
2159 	    linePtr = CreateLine(htPtr);
2160 	    if (linePtr == NULL) {
2161 		goto error;
2162 	    }
2163 	    linePtr->textStart = count;
2164 	    state = 0;
2165 	    break;
2166 
2167 	default:		/* Default action, add to text buffer */
2168 	    textArr[count++] = c;
2169 	    break;
2170 	}
2171     }
2172     if (count > linePtr->textStart) {
2173 	linePtr->textEnd = count;
2174 	textArr[count++] = '\n';/* Every line must end with a '\n' */
2175 	nLines++;
2176     }
2177     Blt_Free(cmdArr);
2178     /* Reset number of lines allocated */
2179     if (ResizeArray((char **)&(htPtr->lineArr), sizeof(Line), nLines,
2180 	    htPtr->arraySize) != TCL_OK) {
2181 	Tcl_AppendResult(interp, "can't reallocate array of lines", (char *)NULL);
2182 	return TCL_ERROR;
2183     }
2184     htPtr->nLines = htPtr->arraySize = nLines;
2185     /*  and the size of the character array */
2186     if (ResizeArray(&(htPtr->charArr), sizeof(char), count,
2187 	    nBytes) != TCL_OK) {
2188 	Tcl_AppendResult(interp, "can't reallocate text character buffer",
2189 	    (char *)NULL);
2190 	return TCL_ERROR;
2191     }
2192     htPtr->nChars = count;
2193     return TCL_OK;
2194   error:
2195     Blt_Free(cmdArr);
2196     return TCL_ERROR;
2197 }
2198 
2199 static int
IncludeText(interp,htPtr,fileName)2200 IncludeText(interp, htPtr, fileName)
2201     Tcl_Interp *interp;
2202     HText *htPtr;
2203     char *fileName;
2204 {
2205     char *buffer;
2206     int result;
2207     int nBytes;
2208 
2209     if ((htPtr->text == NULL) && (fileName == NULL)) {
2210 	return TCL_OK;		/* Empty text string */
2211     }
2212     if (fileName != NULL) {
2213 	nBytes = ReadNamedFile(interp, fileName, &buffer);
2214 	if (nBytes < 0) {
2215 	    return TCL_ERROR;
2216 	}
2217     } else {
2218 	buffer = htPtr->text;
2219 	nBytes = strlen(htPtr->text);
2220     }
2221     result = ParseInput(interp, htPtr, buffer, nBytes);
2222     if (fileName != NULL) {
2223 	Blt_Free(buffer);
2224     }
2225     return result;
2226 }
2227 
2228 /* ARGSUSED */
2229 static char *
TextVarProc(clientData,interp,name1,name2,flags)2230 TextVarProc(clientData, interp, name1, name2, flags)
2231     ClientData clientData;	/* Information about widget. */
2232     Tcl_Interp *interp;		/* Interpreter containing variable. */
2233     char *name1;		/* Name of variable. */
2234     char *name2;		/* Second part of variable name. */
2235     int flags;			/* Information about what happened. */
2236 {
2237     HText *htPtr = clientData;
2238     HText *lasthtPtr;
2239 
2240     /* Check to see of this is the most recent trace */
2241     lasthtPtr = (HText *)Tcl_VarTraceInfo2(interp, name1, name2, flags,
2242 	TextVarProc, NULL);
2243     if (lasthtPtr != htPtr) {
2244 	return NULL;		/* Ignore all but most current trace */
2245     }
2246     if (flags & TCL_TRACE_READS) {
2247 	char c;
2248 
2249 	c = name2[0];
2250 	if ((c == 'w') && (strcmp(name2, "widget") == 0)) {
2251 	    Tcl_SetVar2(interp, name1, name2, Tk_PathName(htPtr->tkwin),
2252 		flags);
2253 	} else if ((c == 'l') && (strcmp(name2, "line") == 0)) {
2254 	    char buf[80];
2255 	    int lineNum;
2256 
2257 	    lineNum = htPtr->nLines - 1;
2258 	    if (lineNum < 0) {
2259 		lineNum = 0;
2260 	    }
2261 	    sprintf(buf, "%d", lineNum);
2262 	    Tcl_SetVar2(interp, name1, name2, buf, flags);
2263 	} else if ((c == 'i') && (strcmp(name2, "index") == 0)) {
2264 	    char buf[80];
2265 
2266 	    sprintf(buf, "%d", htPtr->nChars - 1);
2267 	    Tcl_SetVar2(interp, name1, name2, buf, flags);
2268 	} else if ((c == 'f') && (strcmp(name2, "file") == 0)) {
2269 	    char *fileName;
2270 
2271 	    fileName = htPtr->fileName;
2272 	    if (fileName == NULL) {
2273 		fileName = "";
2274 	    }
2275 	    Tcl_SetVar2(interp, name1, name2, fileName, flags);
2276 	} else {
2277 	    return "?unknown?";
2278 	}
2279     }
2280     return NULL;
2281 }
2282 
2283 static char *varNames[] =
2284 {
2285     "widget", "line", "file", "index", (char *)NULL
2286 };
2287 
2288 static void
CreateTraces(htPtr)2289 CreateTraces(htPtr)
2290     HText *htPtr;
2291 {
2292     char **ptr;
2293     static char globalCmd[] = "global htext";
2294 
2295     /*
2296      * Make the traced variables global to the widget
2297      */
2298     Tcl_Eval(htPtr->interp, globalCmd);
2299     for (ptr = varNames; *ptr != NULL; ptr++) {
2300 	Tcl_TraceVar2(htPtr->interp, "htext", *ptr,
2301 	    (TCL_GLOBAL_ONLY | TCL_TRACE_READS), TextVarProc, htPtr);
2302     }
2303 }
2304 
2305 static void
DeleteTraces(htPtr)2306 DeleteTraces(htPtr)
2307     HText *htPtr;
2308 {
2309     char **ptr;
2310 
2311     for (ptr = varNames; *ptr != NULL; ptr++) {
2312 	Tcl_UntraceVar2(htPtr->interp, "htext", *ptr,
2313 	    (TCL_GLOBAL_ONLY | TCL_TRACE_READS), TextVarProc, htPtr);
2314     }
2315 }
2316 
2317 /*
2318  * ----------------------------------------------------------------------
2319  *
2320  * ConfigureText --
2321  *
2322  * 	This procedure is called to process an argv/argc list, plus
2323  *	the Tk option database, in order to configure (or reconfigure)
2324  *	a hypertext widget.
2325  *
2326  * 	The layout of the text must be calculated (by ComputeLayout)
2327  *	whenever particular options change; -font, -file, -linespacing
2328  *	and -text options. If the user has changes one of these options,
2329  *	it must be detected so that the layout can be recomputed. Since the
2330  *	coordinates of the layout are virtual, there is no need to adjust
2331  *	them if physical window attributes (window size, etc.)
2332  *	change.
2333  *
2334  * Results:
2335  *	The return value is a standard Tcl result.  If TCL_ERROR is
2336  * 	returned, then interp->result contains an error message.
2337  *
2338  * Side effects:
2339  *	Configuration information, such as text string, colors, font,
2340  * 	etc. get set for htPtr;  old resources get freed, if there were any.
2341  * 	The hypertext is redisplayed.
2342  *
2343  * ----------------------------------------------------------------------
2344  */
2345 static int
ConfigureText(interp,htPtr)2346 ConfigureText(interp, htPtr)
2347     Tcl_Interp *interp;		/* Used for error reporting. */
2348     HText *htPtr;		/* Information about widget; may or may not
2349 			         * already have values for some fields. */
2350 {
2351     XGCValues gcValues;
2352     unsigned long gcMask;
2353     GC newGC;
2354 
2355     if (Blt_ConfigModified(configSpecs, interp, "-font", "-linespacing", "-file",
2356 	    "-text", "-width", "-height", (char *)NULL)) {
2357 	/*
2358 	 * These options change the layout of the text.  Width/height
2359 	 * and rows/columns may change a relatively sized window or cavity.
2360 	 */
2361 	htPtr->flags |= (REQUEST_LAYOUT | TEXT_DIRTY);	/* Mark for update */
2362     }
2363     gcMask = GCForeground | GCFont;
2364     gcValues.font = Tk_FontId(htPtr->font);
2365     gcValues.foreground = htPtr->normalFg->pixel;
2366     newGC = Tk_GetGC(htPtr->tkwin, gcMask, &gcValues);
2367     if (htPtr->drawGC != NULL) {
2368 	Tk_FreeGC(htPtr->display, htPtr->drawGC);
2369     }
2370     htPtr->drawGC = newGC;
2371 
2372     gcValues.foreground = htPtr->selFgColor->pixel;
2373     newGC = Tk_GetGC(htPtr->tkwin, gcMask, &gcValues);
2374     if (htPtr->selectGC != NULL) {
2375 	Tk_FreeGC(htPtr->display, htPtr->selectGC);
2376     }
2377     htPtr->selectGC = newGC;
2378 
2379     if (htPtr->xScrollUnits < 1) {
2380 	htPtr->xScrollUnits = 1;
2381     }
2382     if (htPtr->yScrollUnits < 1) {
2383 	htPtr->yScrollUnits = 1;
2384     }
2385     if (htPtr->tile != NULL) {
2386 	Blt_SetTileChangedProc(htPtr->tile, TileChangedProc, htPtr);
2387     }
2388     gcValues.foreground = htPtr->normalBg->pixel;
2389     newGC = Tk_GetGC(htPtr->tkwin, gcMask, &gcValues);
2390     if (htPtr->fillGC != NULL) {
2391 	Tk_FreeGC(htPtr->display, htPtr->fillGC);
2392     }
2393     htPtr->fillGC = newGC;
2394 
2395     if (htPtr->nColumns > 0) {
2396 	htPtr->reqWidth =
2397 	    htPtr->nColumns * Tk_TextWidth(htPtr->font, "0", 1);
2398     }
2399     if (htPtr->nRows > 0) {
2400 	Tk_FontMetrics fontMetrics;
2401 
2402 	Tk_GetFontMetrics(htPtr->font, &fontMetrics);
2403 	htPtr->reqHeight = htPtr->nRows * fontMetrics.linespace;
2404     }
2405     /*
2406      * If the either the -text or -file option changed, read in the
2407      * new text.  The -text option supersedes any -file option.
2408      */
2409     if (Blt_ConfigModified(configSpecs, interp, "-file", "-text", (char *)NULL)) {
2410 	int result;
2411 
2412 	FreeText(htPtr);
2413 	CreateTraces(htPtr);	/* Create variable traces */
2414 
2415 	result = IncludeText(interp, htPtr, htPtr->fileName);
2416 
2417 	DeleteTraces(htPtr);
2418 	if (result == TCL_ERROR) {
2419 	    FreeText(htPtr);
2420 	    return TCL_ERROR;
2421 	}
2422 	ResetTextInfo(htPtr);
2423     }
2424     EventuallyRedraw(htPtr);
2425     return TCL_OK;
2426 }
2427 static void
widgetWorldChanged(ClientData clientData)2428 widgetWorldChanged(ClientData clientData)
2429 {
2430     HText *htPtr = (HText *)clientData;
2431     htPtr->flags |= (TEXT_DIRTY | REQUEST_LAYOUT);
2432     ConfigureText(htPtr->interp, htPtr);
2433     EventuallyRedraw(htPtr);
2434 }
2435 
2436 /* Layout Procedures */
2437 /*
2438  * -----------------------------------------------------------------
2439  *
2440  * TranslateAnchor --
2441  *
2442  * 	Translate the coordinates of a given bounding box based
2443  *	upon the anchor specified.  The anchor indicates where
2444  *	the given xy position is in relation to the bounding box.
2445  *
2446  *  		nw --- n --- ne
2447  *  		|            |     x,y ---+
2448  *  		w   center   e      |     |
2449  *  		|            |      +-----+
2450  *  		sw --- s --- se
2451  *
2452  * Results:
2453  *	The translated coordinates of the bounding box are returned.
2454  *
2455  * -----------------------------------------------------------------
2456  */
2457 static XPoint
TranslateAnchor(deltaX,deltaY,anchor)2458 TranslateAnchor(deltaX, deltaY, anchor)
2459     int deltaX, deltaY;		/* Difference between outer and inner regions
2460 				 */
2461     Tk_Anchor anchor;		/* Direction of the anchor */
2462 {
2463     XPoint point;
2464 
2465     point.x = point.y = 0;
2466     switch (anchor) {
2467     case TK_ANCHOR_NW:		/* Upper left corner */
2468 	break;
2469     case TK_ANCHOR_W:		/* Left center */
2470 	point.y = (deltaY / 2);
2471 	break;
2472     case TK_ANCHOR_SW:		/* Lower left corner */
2473 	point.y = deltaY;
2474 	break;
2475     case TK_ANCHOR_N:		/* Top center */
2476 	point.x = (deltaX / 2);
2477 	break;
2478     case TK_ANCHOR_CENTER:	/* Centered */
2479 	point.x = (deltaX / 2);
2480 	point.y = (deltaY / 2);
2481 	break;
2482     case TK_ANCHOR_S:		/* Bottom center */
2483 	point.x = (deltaX / 2);
2484 	point.y = deltaY;
2485 	break;
2486     case TK_ANCHOR_NE:		/* Upper right corner */
2487 	point.x = deltaX;
2488 	break;
2489     case TK_ANCHOR_E:		/* Right center */
2490 	point.x = deltaX;
2491 	point.y = (deltaY / 2);
2492 	break;
2493     case TK_ANCHOR_SE:		/* Lower right corner */
2494 	point.x = deltaX;
2495 	point.y = deltaY;
2496 	break;
2497     }
2498     return point;
2499 }
2500 
2501 /*
2502  *----------------------------------------------------------------------
2503  *
2504  * ComputeCavitySize --
2505  *
2506  *	Sets the width and height of the cavity based upon the
2507  *	requested size of the embedded widget.  The requested space also
2508  *	includes any external padding which has been designated for
2509  *	this window.
2510  *
2511  * Results:
2512  *	None.
2513  *
2514  * Side Effects:
2515  *	The size of the cavity is set in the embedded widget information
2516  *	structure.  These values can effect how the embedded widget is
2517  *	packed into the master window.
2518  *
2519  *----------------------------------------------------------------------
2520  */
2521 static void
ComputeCavitySize(winPtr)2522 ComputeCavitySize(winPtr)
2523     EmbeddedWidget *winPtr;
2524 {
2525     int width, height;
2526     int twiceBW;
2527 
2528     twiceBW = 2 * Tk_Changes(winPtr->tkwin)->border_width;
2529     if (winPtr->reqCavityWidth > 0) {
2530 	width = winPtr->reqCavityWidth;
2531     } else if (winPtr->relCavityWidth > 0.0) {
2532 	width = (int)((double)Tk_Width(winPtr->htPtr->tkwin) *
2533 	    winPtr->relCavityWidth + 0.5);
2534     } else {
2535 	width = GetEmbeddedWidgetWidth(winPtr) + PADDING(winPtr->padX) +
2536 	    twiceBW;
2537     }
2538     winPtr->cavityWidth = width;
2539 
2540     if (winPtr->reqCavityHeight > 0) {
2541 	height = winPtr->reqCavityHeight;
2542     } else if (winPtr->relCavityHeight > 0.0) {
2543 	height = (int)((double)Tk_Height(winPtr->htPtr->tkwin) *
2544 	    winPtr->relCavityHeight + 0.5);
2545     } else {
2546 	height = GetEmbeddedWidgetHeight(winPtr) + PADDING(winPtr->padY) +
2547 	    twiceBW;
2548     }
2549     winPtr->cavityHeight = height;
2550 }
2551 
2552 /*
2553  *----------------------------------------------------------------------
2554  *
2555  * LayoutLine --
2556  *
2557  *	This procedure computes the total width and height needed
2558  *      to contain the text and widgets for a particular line.
2559  *      It also calculates the baseline of the text on the line with
2560  *	respect to the other widgets on the line.
2561  *
2562  * Results:
2563  *	None.
2564  *
2565  *----------------------------------------------------------------------
2566  */
2567 static void
LayoutLine(htPtr,linePtr)2568 LayoutLine(htPtr, linePtr)
2569     HText *htPtr;
2570     Line *linePtr;
2571 {
2572     EmbeddedWidget *winPtr;
2573     int textStart, textLength;
2574     int maxAscent, maxDescent, maxHeight;
2575     int ascent, descent;
2576     int median;			/* Difference of font ascent/descent values */
2577     Blt_ChainLink *linkPtr;
2578     int x, y;
2579     int newX;
2580     Tk_FontMetrics fontMetrics;
2581 
2582     /* Initialize line defaults */
2583     Tk_GetFontMetrics(htPtr->font, &fontMetrics);
2584     maxAscent = fontMetrics.ascent;
2585     maxDescent = fontMetrics.descent;
2586     median = fontMetrics.ascent - fontMetrics.descent;
2587     ascent = descent = 0;	/* Suppress compiler warnings */
2588 
2589     /*
2590      * Pass 1: Determine the maximum ascent (baseline) and descent
2591      * needed for the line.  We'll need this for figuring the top,
2592      * bottom, and center anchors.
2593      */
2594     for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
2595 	linkPtr = Blt_ChainNextLink(linkPtr)) {
2596 	winPtr = Blt_ChainGetValue(linkPtr);
2597 	if (winPtr->tkwin == NULL) {
2598 	    continue;
2599 	}
2600 	ComputeCavitySize(winPtr);
2601 
2602 	switch (winPtr->justify) {
2603 	case JUSTIFY_TOP:
2604 	    ascent = fontMetrics.ascent + winPtr->padTop;
2605 	    descent = winPtr->cavityHeight - fontMetrics.ascent;
2606 	    break;
2607 	case JUSTIFY_CENTER:
2608 	    ascent = (winPtr->cavityHeight + median) / 2;
2609 	    descent = (winPtr->cavityHeight - median) / 2;
2610 	    break;
2611 	case JUSTIFY_BOTTOM:
2612 	    ascent = winPtr->cavityHeight - fontMetrics.descent;
2613 	    descent = fontMetrics.descent;
2614 	    break;
2615 	}
2616 	if (descent > maxDescent) {
2617 	    maxDescent = descent;
2618 	}
2619 	if (ascent > maxAscent) {
2620 	    maxAscent = ascent;
2621 	}
2622     }
2623 
2624     maxHeight = maxAscent + maxDescent + htPtr->leader;
2625     x = 0;			/* Always starts from x=0 */
2626     y = 0;			/* Suppress compiler warning */
2627     textStart = linePtr->textStart;
2628 
2629     /*
2630      * Pass 2: Find the placements of the text and widgets along each
2631      * line.
2632      */
2633     for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
2634 	linkPtr = Blt_ChainNextLink(linkPtr)) {
2635 	winPtr = Blt_ChainGetValue(linkPtr);
2636 	if (winPtr->tkwin == NULL) {
2637 	    continue;
2638 	}
2639 	/* Get the width of the text leading to the widget. */
2640 	textLength = (winPtr->precedingTextEnd - textStart);
2641 	if (textLength > 0) {
2642 	    Tk_MeasureChars(htPtr->font, htPtr->charArr + textStart,
2643 		textLength, 10000, TK_AT_LEAST_ONE, &newX);
2644 	    winPtr->precedingTextWidth = newX;
2645 	    x += newX;
2646 	}
2647 	switch (winPtr->justify) {
2648 	case JUSTIFY_TOP:
2649 	    y = maxAscent - fontMetrics.ascent;
2650 	    break;
2651 	case JUSTIFY_CENTER:
2652 	    y = maxAscent - (winPtr->cavityHeight + median) / 2;
2653 	    break;
2654 	case JUSTIFY_BOTTOM:
2655 	    y = maxAscent + fontMetrics.descent - winPtr->cavityHeight;
2656 	    break;
2657 	}
2658 	winPtr->x = x, winPtr->y = y;
2659 
2660 	/* Skip over trailing space */
2661 	textStart = winPtr->precedingTextEnd + 1;
2662 
2663 	x += winPtr->cavityWidth;
2664     }
2665 
2666     /*
2667      * This can be either the trailing piece of a line after the last widget
2668      * or the entire line if no widgets are embedded in it.
2669      */
2670     textLength = (linePtr->textEnd - textStart) + 1;
2671     if (textLength > 0) {
2672 	Tk_MeasureChars(htPtr->font, htPtr->charArr + textStart,
2673 	    textLength, 10000, DEF_TEXT_FLAGS, &newX);
2674 	x += newX;
2675     }
2676     /* Update line parameters */
2677     if ((linePtr->width != x) || (linePtr->height != maxHeight) ||
2678 	(linePtr->baseline != maxAscent)) {
2679 	htPtr->flags |= TEXT_DIRTY;
2680     }
2681     linePtr->width = x;
2682     linePtr->height = maxHeight;
2683     linePtr->baseline = maxAscent;
2684 }
2685 
2686 /*
2687  *----------------------------------------------------------------------
2688  *
2689  * ComputeLayout --
2690  *
2691  *	This procedure computes the total width and height needed
2692  *      to contain the text and widgets from all the lines of text.
2693  *      It merely sums the heights and finds the maximum width of
2694  *	all the lines.  The width and height are needed for scrolling.
2695  *
2696  * Results:
2697  *	None.
2698  *
2699  *----------------------------------------------------------------------
2700  */
2701 static void
ComputeLayout(htPtr)2702 ComputeLayout(htPtr)
2703     HText *htPtr;
2704 {
2705     int count;
2706     Line *linePtr;
2707     int height, width;
2708 
2709     width = height = 0;
2710     for (count = 0; count < htPtr->nLines; count++) {
2711 	linePtr = htPtr->lineArr + count;
2712 
2713 	linePtr->offset = height;
2714 	LayoutLine(htPtr, linePtr);
2715 	height += linePtr->height;
2716 	if (linePtr->width > width) {
2717 	    width = linePtr->width;
2718 	}
2719     }
2720     /*
2721      * Set changed flag if new layout changed size of virtual text.
2722      */
2723     if ((height != htPtr->worldHeight) || (width != htPtr->worldWidth)) {
2724 	htPtr->worldHeight = height, htPtr->worldWidth = width;
2725 	htPtr->flags |= TEXT_DIRTY;
2726     }
2727 }
2728 
2729 /* Display Procedures */
2730 /*
2731  * ----------------------------------------------------------------------
2732  *
2733  * GetVisibleLines --
2734  *
2735  * 	Calculates which lines are visible using the height
2736  *      of the viewport and y offset from the top of the text.
2737  *
2738  * Results:
2739  *	None.
2740  *
2741  * Side effects:
2742  *	Only those line between first and last inclusive are
2743  * 	redrawn.
2744  *
2745  * ----------------------------------------------------------------------
2746  */
2747 static int
GetVisibleLines(htPtr)2748 GetVisibleLines(htPtr)
2749     HText *htPtr;
2750 {
2751     int topLine, bottomLine;
2752     int firstY, lastY;
2753     int lastLine;
2754 
2755     if (htPtr->nLines == 0) {
2756 	htPtr->first = 0;
2757 	htPtr->last = -1;
2758 	return TCL_OK;
2759     }
2760     firstY = htPtr->pendingY;
2761     lastLine = htPtr->nLines - 1;
2762 
2763     /* First line */
2764     topLine = LineSearch(htPtr, firstY, 0, lastLine);
2765     if (topLine < 0) {
2766 	/*
2767 	 * This can't be. The y-coordinate offset must be corrupted.
2768 	 */
2769 	fprintf(stderr, "internal error: First position not found `%d'\n",
2770 	    firstY);
2771 	return TCL_ERROR;
2772     }
2773     htPtr->first = topLine;
2774 
2775     /*
2776      * If there is less text than window space, the bottom line is the
2777      * last line of text.  Otherwise search for the line at the bottom
2778      * of the window.
2779      */
2780     lastY = firstY + Tk_Height(htPtr->tkwin) - 1;
2781     if (lastY < htPtr->worldHeight) {
2782 	bottomLine = LineSearch(htPtr, lastY, topLine, lastLine);
2783     } else {
2784 	bottomLine = lastLine;
2785     }
2786     if (bottomLine < 0) {
2787 	/*
2788 	 * This can't be. The newY offset must be corrupted.
2789 	 */
2790 	fprintf(stderr, "internal error: Last position not found `%d'\n",
2791 	    lastY);
2792 #ifdef notdef
2793 	fprintf(stderr, "worldHeight=%d,height=%d,top=%d,first=%d,last=%d\n",
2794 	    htPtr->worldHeight, Tk_Height(htPtr->tkwin), firstY,
2795 	    htPtr->lineArr[topLine].offset, htPtr->lineArr[lastLine].offset);
2796 #endif
2797 	return TCL_ERROR;
2798     }
2799     htPtr->last = bottomLine;
2800     return TCL_OK;
2801 }
2802 
2803 /*
2804  * ----------------------------------------------------------------------
2805  *
2806  * DrawSegment --
2807  *
2808  * 	Draws a line segment, designated by the segment structure.
2809  *	This routine handles the display of selected text by drawing
2810  *	a raised 3D border underneath the selected text.
2811  *
2812  * Results:
2813  *	None.
2814  *
2815  * Side effects:
2816  *	The line segment is drawn on *draw*.
2817  *
2818  * ----------------------------------------------------------------------
2819  */
2820 static void
DrawSegment(htPtr,draw,linePtr,x,y,segPtr)2821 DrawSegment(htPtr, draw, linePtr, x, y, segPtr)
2822     HText *htPtr;
2823     Drawable draw;
2824     Line *linePtr;
2825     int x, y;
2826     Segment *segPtr;
2827 {
2828     int lastX, curPos, nChars;
2829     int textLength;
2830     int selStart, selEnd, selLength;
2831     Tk_FontMetrics fontMetrics;
2832 
2833 #ifdef notdef
2834     fprintf(stderr, "DS select: first=%d,last=%d text: first=%d,last=%d\n",
2835 	htPtr->selFirst, htPtr->selLast, segPtr->textStart, segPtr->textEnd);
2836 #endif
2837     textLength = (segPtr->textEnd - segPtr->textStart) + 1;
2838     if (textLength < 1) {
2839 	return;
2840     }
2841     Tk_GetFontMetrics(htPtr->font, &fontMetrics);
2842     if ((segPtr->textEnd < htPtr->selFirst) ||
2843 	(segPtr->textStart > htPtr->selLast)) {	/* No selected text */
2844 	Tk_DrawChars(htPtr->display, draw, htPtr->drawGC, htPtr->font,
2845 	    htPtr->charArr + segPtr->textStart, textLength - 1,
2846 	    x, y + linePtr->baseline);
2847 	return;
2848     }
2849     /*
2850      *	Text in a segment (with selected text) may have
2851      *	up to three regions:
2852      *
2853      *	1) the text before the start the selection
2854      *	2) the selected text itself (drawn in a raised border)
2855      *	3) the text following the selection.
2856      */
2857 
2858     selStart = segPtr->textStart;
2859     selEnd = segPtr->textEnd;
2860     if (htPtr->selFirst > segPtr->textStart) {
2861 	selStart = htPtr->selFirst;
2862     }
2863     if (htPtr->selLast < segPtr->textEnd) {
2864 	selEnd = htPtr->selLast;
2865     }
2866     selLength = (selEnd - selStart) + 1;
2867     lastX = x;
2868     curPos = segPtr->textStart;
2869 
2870     if (selStart > segPtr->textStart) {	/* Text preceding selection */
2871 	nChars = (selStart - segPtr->textStart);
2872 	Tk_MeasureChars(htPtr->font, htPtr->charArr + segPtr->textStart,
2873 	    nChars, 10000, DEF_TEXT_FLAGS, &lastX);
2874 	lastX += x;
2875 	Tk_DrawChars(htPtr->display, draw, htPtr->drawGC, htPtr->font,
2876 	    htPtr->charArr + segPtr->textStart, nChars, x,
2877 	    y + linePtr->baseline);
2878 	curPos = selStart;
2879     }
2880     if (selLength > 0) {	/* The selection itself */
2881 	int width, nextX;
2882 
2883 	Tk_MeasureChars(htPtr->font, htPtr->charArr + selStart,
2884 	    selLength, 10000, DEF_TEXT_FLAGS, &nextX);
2885 	nextX += x;
2886 	width = (selEnd == linePtr->textEnd)
2887 	    ? htPtr->worldWidth - htPtr->xOffset - lastX :
2888 	    nextX - lastX;
2889 	Blt_Fill3DRectangle(htPtr->tkwin, draw, htPtr->selBorder,
2890 	    lastX, y + linePtr->baseline - fontMetrics.ascent,
2891 	    width, fontMetrics.linespace, htPtr->selBorderWidth,
2892 	    TK_RELIEF_RAISED);
2893 	Tk_DrawChars(htPtr->display, draw, htPtr->selectGC,
2894 	    htPtr->font, htPtr->charArr + selStart, selLength,
2895 	    lastX, y + linePtr->baseline);
2896 	lastX = nextX;
2897 	curPos = selStart + selLength;
2898     }
2899     nChars = segPtr->textEnd - curPos;
2900     if (nChars > 0) {		/* Text following the selection */
2901 	Tk_DrawChars(htPtr->display, draw, htPtr->drawGC, htPtr->font,
2902 	    htPtr->charArr + curPos, nChars - 1, lastX, y + linePtr->baseline);
2903     }
2904 }
2905 
2906 /*
2907  * ----------------------------------------------------------------------
2908  *
2909  * MoveEmbeddedWidget --
2910  *
2911  * 	Move a embedded widget to a new location in the hypertext
2912  *	parent window.  If the window has no geometry (i.e. width,
2913  *	or height is 0), simply unmap to window.
2914  *
2915  * Results:
2916  *	None.
2917  *
2918  * Side effects:
2919  *	Each embedded widget is moved to its new location, generating
2920  *      Expose events in the parent for each embedded widget moved.
2921  *
2922  * ----------------------------------------------------------------------
2923  */
2924 static void
MoveEmbeddedWidget(winPtr,offset)2925 MoveEmbeddedWidget(winPtr, offset)
2926     EmbeddedWidget *winPtr;
2927     int offset;
2928 {
2929     int winWidth, winHeight;
2930     int width, height;
2931     int deltaX, deltaY;
2932     int x, y;
2933     int intBW;
2934 
2935     winWidth = GetEmbeddedWidgetWidth(winPtr);
2936     winHeight = GetEmbeddedWidgetHeight(winPtr);
2937     if ((winWidth < 1) || (winHeight < 1)) {
2938 	if (Tk_IsMapped(winPtr->tkwin)) {
2939 	    Tk_UnmapWindow(winPtr->tkwin);
2940 	}
2941 	return;
2942     }
2943     intBW = Tk_Changes(winPtr->tkwin)->border_width;
2944     x = (winPtr->x + intBW + winPtr->padLeft) -
2945 	winPtr->htPtr->xOffset;
2946     y = offset + (winPtr->y + intBW + winPtr->padTop) -
2947 	winPtr->htPtr->yOffset;
2948 
2949     width = winPtr->cavityWidth - (2 * intBW + PADDING(winPtr->padX));
2950     if (width < 0) {
2951 	width = 0;
2952     }
2953     if ((width < winWidth) || (winPtr->fill & FILL_X)) {
2954 	winWidth = width;
2955     }
2956     deltaX = width - winWidth;
2957 
2958     height = winPtr->cavityHeight - (2 * intBW + PADDING(winPtr->padY));
2959     if (height < 0) {
2960 	height = 0;
2961     }
2962     if ((height < winHeight) || (winPtr->fill & FILL_Y)) {
2963 	winHeight = height;
2964     }
2965     deltaY = height - winHeight;
2966 
2967     if ((deltaX > 0) || (deltaY > 0)) {
2968 	XPoint point;
2969 
2970 	point = TranslateAnchor(deltaX, deltaY, winPtr->anchor);
2971 	x += point.x, y += point.y;
2972     }
2973     winPtr->winWidth = winWidth;
2974     winPtr->winHeight = winHeight;
2975 
2976     if ((x != Tk_X(winPtr->tkwin)) || (y != Tk_Y(winPtr->tkwin)) ||
2977 	(winWidth != Tk_Width(winPtr->tkwin)) ||
2978 	(winHeight != Tk_Height(winPtr->tkwin))) {
2979 	Tk_MoveResizeWindow(winPtr->tkwin, x, y, winWidth, winHeight);
2980     }
2981     if (!Tk_IsMapped(winPtr->tkwin)) {
2982 	Tk_MapWindow(winPtr->tkwin);
2983     }
2984 }
2985 
2986 /*
2987  * ----------------------------------------------------------------------
2988  *
2989  * DrawPage --
2990  *
2991  * 	This procedure displays the lines of text and moves the widgets
2992  *      to their new positions.  It draws lines with regard to
2993  *	the direction of the scrolling.  The idea here is to make the
2994  *	text and buttons appear to move together. Otherwise you will
2995  *	get a "jiggling" effect where the windows appear to bump into
2996  *	the next line before that line is moved.  In the worst case, where
2997  *	every line has at least one widget, you can get an aquarium effect
2998  *      (lines appear to ripple up).
2999  *
3000  * 	The text area may start between line boundaries (to accommodate
3001  *	both variable height lines and constant scrolling). Subtract the
3002  *	difference of the page offset and the line offset from the starting
3003  *	coordinates. For horizontal scrolling, simply subtract the offset
3004  *	of the viewport. The window will clip the top of the first line,
3005  *	the bottom of the last line, whatever text extends to the left
3006  *	or right of the viewport on any line.
3007  *
3008  * Results:
3009  *	None.
3010  *
3011  * Side effects:
3012  *	Commands are output to X to display the line in its current
3013  * 	mode.
3014  *
3015  * ----------------------------------------------------------------------
3016  */
3017 static void
DrawPage(htPtr,deltaY)3018 DrawPage(htPtr, deltaY)
3019     HText *htPtr;
3020     int deltaY;			/* Change from previous Y coordinate */
3021 {
3022     Line *linePtr;
3023     EmbeddedWidget *winPtr;
3024     Tk_Window tkwin = htPtr->tkwin;
3025     Segment sgmt;
3026     Pixmap pixmap;
3027     int forceCopy;
3028     int i;
3029     int lineNum;
3030     int x, y, lastY;
3031     Blt_ChainLink *linkPtr;
3032     int width, height;
3033     Display *display;
3034 
3035     display = htPtr->display;
3036     width = Tk_Width(tkwin);
3037     height = Tk_Height(tkwin);
3038 
3039     /* Create an off-screen pixmap for semi-smooth scrolling. */
3040     pixmap = Tk_GetPixmap(display, Tk_WindowId(tkwin), width, height,
3041 	  Tk_Depth(tkwin));
3042 
3043     x = -(htPtr->xOffset);
3044     y = -(htPtr->yOffset);
3045 
3046     if (Blt_HasTile(htPtr->tile)) {
3047 	if (htPtr->tileOffsetPage) {
3048 	    Blt_SetTSOrigin(htPtr->tkwin, htPtr->tile, x, y);
3049 	} else {
3050 	    Blt_SetTileOrigin(htPtr->tkwin, htPtr->tile, 0, 0);
3051 	}
3052 	Blt_TileRectangle(htPtr->tkwin, pixmap, htPtr->tile, 0, 0, width,
3053 		height);
3054     } else {
3055 	XFillRectangle(display, pixmap, htPtr->fillGC, 0, 0, width, height);
3056     }
3057 
3058 
3059     if (! htPtr->lineArr) return;
3060     if (deltaY >= 0) {
3061 	y += htPtr->lineArr[htPtr->first].offset;
3062 	lineNum = htPtr->first;
3063 	lastY = 0;
3064     } else {
3065 	y += htPtr->lineArr[htPtr->last].offset;
3066 	lineNum = htPtr->last;
3067 	lastY = height;
3068     }
3069     forceCopy = 0;
3070 
3071     /* Draw each line */
3072     for (i = htPtr->first; i <= htPtr->last; i++) {
3073 
3074 	/* Initialize character position in text buffer to start */
3075 	linePtr = htPtr->lineArr + lineNum;
3076 	sgmt.textStart = linePtr->textStart;
3077 	sgmt.textEnd = linePtr->textEnd;
3078 
3079 	/* Initialize X position */
3080 	x = -(htPtr->xOffset);
3081 	for (linkPtr = Blt_ChainFirstLink(linePtr->chainPtr); linkPtr != NULL;
3082 	    linkPtr = Blt_ChainNextLink(linkPtr)) {
3083 	    winPtr = Blt_ChainGetValue(linkPtr);
3084 
3085 	    if (winPtr->tkwin != NULL) {
3086 		winPtr->flags |= WIDGET_VISIBLE;
3087 		MoveEmbeddedWidget(winPtr, linePtr->offset);
3088 	    }
3089 	    sgmt.textEnd = winPtr->precedingTextEnd - 1;
3090 	    if (sgmt.textEnd >= sgmt.textStart) {
3091 		DrawSegment(htPtr, pixmap, linePtr, x, y, &sgmt);
3092 		x += winPtr->precedingTextWidth;
3093 	    }
3094 	    /* Skip over the extra trailing space which designates the widget */
3095 	    sgmt.textStart = winPtr->precedingTextEnd + 1;
3096 	    x += winPtr->cavityWidth;
3097 	    forceCopy++;
3098 	}
3099 
3100 	/*
3101 	 * This may be the text trailing the last widget or the entire
3102 	 * line if no widgets occur on it.
3103 	 */
3104 	sgmt.textEnd = linePtr->textEnd;
3105 	if (sgmt.textEnd >= sgmt.textStart) {
3106 	    DrawSegment(htPtr, pixmap, linePtr, x, y, &sgmt);
3107 	}
3108 	/* Go to the top of the next line */
3109 	if (deltaY >= 0) {
3110 	    y += htPtr->lineArr[lineNum].height;
3111 	    lineNum++;
3112 	}
3113 	if ((forceCopy > 0) && !(htPtr->flags & TEXT_DIRTY)) {
3114 	    if (deltaY >= 0) {
3115 		XCopyArea(display, pixmap, Tk_WindowId(tkwin), htPtr->drawGC,
3116 			  0, lastY, width, y - lastY, 0, lastY);
3117 	    } else {
3118 		XCopyArea(display, pixmap, Tk_WindowId(tkwin), htPtr->drawGC,
3119 			  0, y, width, lastY - y, 0, y);
3120 	    }
3121 	    forceCopy = 0;	/* Reset drawing flag */
3122 	    lastY = y;		/* Record last Y position */
3123 	}
3124 	if ((deltaY < 0) && (lineNum > 0)) {
3125 	    --lineNum;
3126 	    y -= htPtr->lineArr[lineNum].height;
3127 	}
3128     }
3129     /*
3130      * If the viewport was resized, draw the page in one operation.
3131      * Otherwise draw any left-over block of text (either at the top
3132      * or bottom of the page)
3133      */
3134     if (htPtr->flags & TEXT_DIRTY) {
3135 	XCopyArea(display, pixmap, Tk_WindowId(tkwin),
3136 	    htPtr->drawGC, 0, 0, width, height, 0, 0);
3137     } else if (lastY != y) {
3138 	if (deltaY >= 0) {
3139 	    height -= lastY;
3140 	    XCopyArea(display, pixmap, Tk_WindowId(tkwin),
3141 		htPtr->drawGC, 0, lastY, width, height, 0, lastY);
3142 	} else {
3143 	    height = lastY;
3144 	    XCopyArea(display, pixmap, Tk_WindowId(tkwin),
3145 		htPtr->drawGC, 0, 0, width, height, 0, 0);
3146 	}
3147     }
3148     Tk_FreePixmap(display, pixmap);
3149 }
3150 
3151 
3152 static void
SendBogusEvent(tkwin)3153 SendBogusEvent(tkwin)
3154     Tk_Window tkwin;
3155 {
3156 #define DONTPROPAGATE 0
3157     XEvent event;
3158 
3159     event.type = event.xexpose.type = Expose;
3160     event.xexpose.window = Tk_WindowId(tkwin);
3161     event.xexpose.display = Tk_Display(tkwin);
3162     event.xexpose.count = 0;
3163     event.xexpose.x = event.xexpose.y = 0;
3164     event.xexpose.width = Tk_Width(tkwin);
3165     event.xexpose.height = Tk_Height(tkwin);
3166 
3167     XSendEvent(Tk_Display(tkwin), Tk_WindowId(tkwin), DONTPROPAGATE,
3168 	ExposureMask, &event);
3169 }
3170 
3171 /*
3172  * ----------------------------------------------------------------------
3173  *
3174  * DisplayText --
3175  *
3176  * 	This procedure is invoked to display a hypertext widget.
3177  *	Many of the operations which might ordinarily be performed
3178  *	elsewhere (e.g. in a configuration routine) are done here
3179  *	because of the somewhat unusual interactions occurring between
3180  *	the parent and embedded widgets.
3181  *
3182  *      Recompute the layout of the text if necessary. This is
3183  *	necessary if the world coordinate system has changed.
3184  *	Specifically, the following may have occurred:
3185  *
3186  *	  1.  a text attribute has changed (font, linespacing, etc.).
3187  *	  2.  widget option changed (anchor, width, height).
3188  *        3.  actual embedded widget was resized.
3189  *	  4.  new text string or file.
3190  *
3191  *      This is deferred to the display routine since potentially
3192  *      many of these may occur (especially embedded widget changes).
3193  *
3194  *	Set the vertical and horizontal scrollbars (if they are
3195  *	designated) by issuing a Tcl command.  Done here since
3196  *	the text window width and height are needed.
3197  *
3198  *	If the viewport position or contents have changed in the
3199  *	vertical direction,  the now out-of-view embedded widgets
3200  *	must be moved off the viewport.  Since embedded widgets will
3201  *	obscure the text window, it is imperative that the widgets
3202  *	are moved off before we try to redraw text in the same area.
3203  *      This is necessary only for vertical movements.  Horizontal
3204  *	embedded widget movements are handled automatically in the
3205  *	page drawing routine.
3206  *
3207  *      Get the new first and last line numbers for the viewport.
3208  *      These line numbers may have changed because either a)
3209  *      the viewport changed size or position, or b) the text
3210  *	(embedded widget sizes or text attributes) have changed.
3211  *
3212  *	If the viewport has changed vertically (i.e. the first or
3213  *      last line numbers have changed), move the now out-of-view
3214  *	embedded widgets off the viewport.
3215  *
3216  *      Potentially many expose events may be generated when the
3217  *	the individual embedded widgets are moved and/or resized.
3218  *	These events need to be ignored.  Since (I think) expose
3219  * 	events are guaranteed to happen in order, we can bracket
3220  *	them by sending phony events (via XSendEvent). The phony
3221  *      events turn on and off flags indicating which events
3222 *	should be ignored.
3223  *
3224  *	Finally, the page drawing routine is called.
3225  *
3226  * Results:
3227  *	None.
3228  *
3229  * Side effects:
3230  * 	Commands are output to X to display the hypertext in its
3231  *	current mode.
3232  *
3233  * ----------------------------------------------------------------------
3234  */
3235 static void
DisplayText(clientData)3236 DisplayText(clientData)
3237     ClientData clientData;	/* Information about widget. */
3238 {
3239     HText *htPtr = clientData;
3240     Tk_Window tkwin = htPtr->tkwin;
3241     int oldFirst;		/* First line of old viewport */
3242     int oldLast;		/* Last line of old viewport */
3243     int deltaY;			/* Change in viewport in Y direction */
3244     int reqWidth, reqHeight;
3245 
3246 #ifdef notdef
3247     fprintf(stderr, "calling DisplayText(%s)\n", Tk_PathName(htPtr->tkwin));
3248 #endif
3249     htPtr->flags &= ~REDRAW_PENDING;
3250     if (tkwin == NULL) {
3251 	return;			/* Window has been destroyed */
3252     }
3253     if (htPtr->flags & REQUEST_LAYOUT) {
3254 	/*
3255 	 * Recompute the layout when widgets are created, deleted,
3256 	 * moved, or resized.  Also when text attributes (such as
3257 	 * font, linespacing) have changed.
3258 	 */
3259 	ComputeLayout(htPtr);
3260     }
3261     htPtr->lastWidth = Tk_Width(tkwin);
3262     htPtr->lastHeight = Tk_Height(tkwin);
3263 
3264     /*
3265      * Check the requested width and height.  We allow two modes:
3266      * 	1) If the user requested value is greater than zero, use it.
3267      *  2) Otherwise, let the window be as big as the virtual text.
3268      *	   This could be too large to display, so constrain it by
3269      *	   the maxWidth and maxHeight values.
3270      *
3271      * In any event, we need to calculate the size of the virtual
3272      * text and then make a geometry request.  This is so that widgets
3273      * whose size is relative to the master, will be set once.
3274      */
3275     if (htPtr->reqWidth > 0) {
3276 	reqWidth = htPtr->reqWidth;
3277     } else {
3278 	reqWidth = MIN(htPtr->worldWidth, htPtr->maxWidth);
3279 	if (reqWidth < 1) {
3280 	    reqWidth = 1;
3281 	}
3282     }
3283     if (htPtr->reqHeight > 0) {
3284 	reqHeight = htPtr->reqHeight;
3285     } else {
3286 	reqHeight = MIN(htPtr->worldHeight, htPtr->maxHeight);
3287 	if (reqHeight < 1) {
3288 	    reqHeight = 1;
3289 	}
3290     }
3291     if ((reqWidth != Tk_ReqWidth(tkwin)) || (reqHeight != Tk_ReqHeight(tkwin))) {
3292 	Tk_GeometryRequest(tkwin, reqWidth, reqHeight);
3293 
3294 	EventuallyRedraw(htPtr);
3295 	return;			/* Try again with new geometry */
3296     }
3297     if (!Tk_IsMapped(tkwin)) {
3298 	return;
3299     }
3300     /*
3301      * Turn off layout requests here, after the text window has been
3302      * mapped.  Otherwise, relative embedded widget size requests wrt
3303      * to the size of parent text window will be wrong.
3304      */
3305     htPtr->flags &= ~REQUEST_LAYOUT;
3306 
3307     /* Is there a pending goto request? */
3308     if (htPtr->flags & GOTO_PENDING) {
3309 	htPtr->pendingY = htPtr->lineArr[htPtr->reqLineNum].offset;
3310 	htPtr->flags &= ~GOTO_PENDING;
3311     }
3312     deltaY = htPtr->pendingY - htPtr->yOffset;
3313     oldFirst = htPtr->first, oldLast = htPtr->last;
3314 
3315     /*
3316      * If the viewport has changed size or position, or the text
3317      * and/or embedded widgets have changed, adjust the scrollbars to
3318      * new positions.
3319      */
3320     if (htPtr->flags & TEXT_DIRTY) {
3321 	int width, height;
3322 
3323 	width = Tk_Width(htPtr->tkwin);
3324 	height = Tk_Height(htPtr->tkwin);
3325 
3326 	/* Reset viewport origin and world extents */
3327 	htPtr->xOffset = Blt_AdjustViewport(htPtr->pendingX,
3328 	    htPtr->worldWidth, width,
3329 	    htPtr->xScrollUnits, BLT_SCROLL_MODE_LISTBOX);
3330 	htPtr->yOffset = Blt_AdjustViewport(htPtr->pendingY,
3331 	    htPtr->worldHeight, height,
3332 	    htPtr->yScrollUnits, BLT_SCROLL_MODE_LISTBOX);
3333 	if (htPtr->xScrollCmdPrefix != NULL) {
3334 	    Blt_UpdateScrollbar(htPtr->interp, htPtr->xScrollCmdPrefix,
3335 		(double)htPtr->xOffset / htPtr->worldWidth,
3336 		(double)(htPtr->xOffset + width) / htPtr->worldWidth);
3337 	}
3338 	if (htPtr->yScrollCmdPrefix != NULL) {
3339 	    Blt_UpdateScrollbar(htPtr->interp, htPtr->yScrollCmdPrefix,
3340 		(double)htPtr->yOffset / htPtr->worldHeight,
3341 		(double)(htPtr->yOffset + height) / htPtr->worldHeight);
3342 	}
3343 	/*
3344 	 * Given a new viewport or text height, find the first and
3345 	 * last line numbers of the new viewport.
3346 	 */
3347 	if (GetVisibleLines(htPtr) != TCL_OK) {
3348 	    return;
3349 	}
3350     }
3351     /*
3352      * 	This is a kludge: Send an expose event before and after
3353      * 	drawing the page of text.  Since moving and resizing of the
3354      * 	embedded widgets will cause redundant expose events in the parent
3355      * 	window, the phony events will bracket them indicating no
3356      * 	action should be taken.
3357      */
3358     SendBogusEvent(tkwin);
3359 
3360     /*
3361      * If either the position of the viewport has changed or the size
3362      * of width or height of the entire text have changed, move the
3363      * widgets from the previous viewport out of the current
3364      * viewport. Worry only about the vertical embedded widget movements.
3365      * The page is always draw at full width and the viewport will clip
3366      * the text.
3367      */
3368     if ((htPtr->first != oldFirst) || (htPtr->last != oldLast)) {
3369 	int offset;
3370 	int i;
3371 	int first, last;
3372 	Blt_ChainLink *linkPtr;
3373 	EmbeddedWidget *winPtr;
3374 
3375 	/* Figure out which lines are now out of the viewport */
3376 
3377 	if ((htPtr->first > oldFirst) && (htPtr->first <= oldLast)) {
3378 	    first = oldFirst, last = htPtr->first;
3379 	} else if ((htPtr->last < oldLast) && (htPtr->last >= oldFirst)) {
3380 	    first = htPtr->last, last = oldLast;
3381 	} else {
3382 	    first = oldFirst, last = oldLast;
3383 	}
3384         if (! htPtr->lineArr) return;
3385 
3386 	for (i = first; i <= last; i++) {
3387 	    offset = htPtr->lineArr[i].offset;
3388 	    for (linkPtr = Blt_ChainFirstLink(htPtr->lineArr[i].chainPtr);
3389 		linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
3390 		winPtr = Blt_ChainGetValue(linkPtr);
3391 		if (winPtr->tkwin != NULL) {
3392 		    MoveEmbeddedWidget(winPtr, offset);
3393 		    winPtr->flags &= ~WIDGET_VISIBLE;
3394 		}
3395 	    }
3396 	}
3397     }
3398     DrawPage(htPtr, deltaY);
3399     SendBogusEvent(tkwin);
3400 
3401     /* Reset flags */
3402     htPtr->flags &= ~TEXT_DIRTY;
3403 }
3404 
3405 /* Selection Procedures */
3406 /*
3407  *----------------------------------------------------------------------
3408  *
3409  * TextSelectionProc --
3410  *
3411  *	This procedure is called back by Tk when the selection is
3412  *	requested by someone.  It returns part or all of the selection
3413  *	in a buffer provided by the caller.
3414  *
3415  * Results:
3416  *	The return value is the number of non-NULL bytes stored
3417  *	at buffer.  Buffer is filled (or partially filled) with a
3418  *	NULL-terminated string containing part or all of the selection,
3419  *	as given by offset and maxBytes.
3420  *
3421  * Side effects:
3422  *	None.
3423  *
3424  *----------------------------------------------------------------------
3425  */
3426 static int
TextSelectionProc(clientData,offset,buffer,maxBytes)3427 TextSelectionProc(clientData, offset, buffer, maxBytes)
3428     ClientData clientData;	/* Information about Text widget. */
3429     int offset;			/* Offset within selection of first
3430 				 * character to be returned. */
3431     char *buffer;		/* Location in which to place
3432 				 * selection. */
3433     int maxBytes;		/* Maximum number of bytes to place
3434 				 * at buffer, not including terminating
3435 				 * NULL character. */
3436 {
3437     HText *htPtr = clientData;
3438     int size;
3439 
3440     if ((htPtr->selFirst < 0) || (!htPtr->exportSelection)) {
3441 	return -1;
3442     }
3443     size = (htPtr->selLast - htPtr->selFirst) + 1 - offset;
3444     if (size > maxBytes) {
3445 	size = maxBytes;
3446     }
3447     if (size <= 0) {
3448 	return 0;		/* huh? */
3449     }
3450     strncpy(buffer, htPtr->charArr + htPtr->selFirst + offset, size);
3451     buffer[size] = '\0';
3452     return size;
3453 }
3454 
3455 /*
3456  *----------------------------------------------------------------------
3457  *
3458  * TextLostSelection --
3459  *
3460  *	This procedure is called back by Tk when the selection is
3461  *	grabbed away from a Text widget.
3462  *
3463  * Results:
3464  *	None.
3465  *
3466  * Side effects:
3467  *	The existing selection is unhighlighted, and the window is
3468  *	marked as not containing a selection.
3469  *
3470  *----------------------------------------------------------------------
3471  */
3472 static void
TextLostSelection(clientData)3473 TextLostSelection(clientData)
3474     ClientData clientData;	/* Information about Text widget. */
3475 {
3476     HText *htPtr = clientData;
3477 
3478     if ((htPtr->selFirst >= 0) && (htPtr->exportSelection)) {
3479 	htPtr->selFirst = htPtr->selLast = -1;
3480 	EventuallyRedraw(htPtr);
3481     }
3482 }
3483 
3484 /*
3485  *----------------------------------------------------------------------
3486  *
3487  * SelectLine --
3488  *
3489  *	Modify the selection by moving both its anchored and un-anchored
3490  *	ends.  This could make the selection either larger or smaller.
3491  *
3492  * Results:
3493  *	None.
3494  *
3495  * Side effects:
3496  *	The selection changes.
3497  *
3498  *----------------------------------------------------------------------
3499  */
3500 static int
SelectLine(htPtr,tindex)3501 SelectLine(htPtr, tindex)
3502     HText *htPtr;		/* Information about widget. */
3503     int tindex;			/* Index of element that is to
3504 				 * become the "other" end of the
3505 				 * selection. */
3506 {
3507     int selFirst, selLast;
3508     int lineNum;
3509     Line *linePtr;
3510 
3511     lineNum = IndexSearch(htPtr, tindex, 0, htPtr->nLines - 1);
3512     if (lineNum < 0) {
3513 	char string[200];
3514 
3515 	sprintf(string, "can't determine line number from index \"%d\"",
3516 	    tindex);
3517 	Tcl_AppendResult(htPtr->interp, string, (char *)NULL);
3518 	return TCL_ERROR;
3519     }
3520     linePtr = htPtr->lineArr + lineNum;
3521     /*
3522      * Grab the selection if we don't own it already.
3523      */
3524     if ((htPtr->exportSelection) && (htPtr->selFirst == -1)) {
3525 	Tk_OwnSelection(htPtr->tkwin, XA_PRIMARY, TextLostSelection, htPtr);
3526     }
3527     selFirst = linePtr->textStart;
3528     selLast = linePtr->textEnd;
3529     htPtr->selAnchor = tindex;
3530     if ((htPtr->selFirst != selFirst) ||
3531 	(htPtr->selLast != selLast)) {
3532 	htPtr->selFirst = selFirst;
3533 	htPtr->selLast = selLast;
3534 	EventuallyRedraw(htPtr);
3535     }
3536     return TCL_OK;
3537 }
3538 
3539 /*
3540  *----------------------------------------------------------------------
3541  *
3542  * SelectWord --
3543  *
3544  *	Modify the selection by moving both its anchored and un-anchored
3545  *	ends.  This could make the selection either larger or smaller.
3546  *
3547  * Results:
3548  *	None.
3549  *
3550  * Side effects:
3551  *	The selection changes.
3552  *
3553  *----------------------------------------------------------------------
3554  */
3555 static int
SelectWord(htPtr,tindex)3556 SelectWord(htPtr, tindex)
3557     HText *htPtr;		/* Information about widget. */
3558     int tindex;			/* Index of element that is to
3559 				 * become the "other" end of the
3560 				 * selection. */
3561 {
3562     int selFirst, selLast;
3563     int i;
3564 
3565     for (i = tindex; i < htPtr->nChars; i++) {
3566 	if (isspace(UCHAR(htPtr->charArr[i]))) {
3567 	    break;
3568 	}
3569     }
3570     selLast = i - 1;
3571     for (i = tindex; i >= 0; i--) {
3572 	if (isspace(UCHAR(htPtr->charArr[i]))) {
3573 	    break;
3574 	}
3575     }
3576     selFirst = i + 1;
3577     if (selFirst > selLast) {
3578 	selFirst = selLast = tindex;
3579     }
3580     /*
3581      * Grab the selection if we don't own it already.
3582      */
3583     if ((htPtr->exportSelection) && (htPtr->selFirst == -1)) {
3584 	Tk_OwnSelection(htPtr->tkwin, XA_PRIMARY, TextLostSelection, htPtr);
3585     }
3586     htPtr->selAnchor = tindex;
3587     if ((htPtr->selFirst != selFirst) || (htPtr->selLast != selLast)) {
3588 	htPtr->selFirst = selFirst, htPtr->selLast = selLast;
3589 	EventuallyRedraw(htPtr);
3590     }
3591     return TCL_OK;
3592 }
3593 
3594 /*
3595  *----------------------------------------------------------------------
3596  *
3597  * SelectTextBlock --
3598  *
3599  *	Modify the selection by moving its un-anchored end.  This
3600  *	could make the selection either larger or smaller.
3601  *
3602  * Results:
3603  *	None.
3604  *
3605  * Side effects:
3606  *	The selection changes.
3607  *
3608  *----------------------------------------------------------------------
3609  */
3610 static int
SelectTextBlock(htPtr,tindex)3611 SelectTextBlock(htPtr, tindex)
3612     HText *htPtr;		/* Information about widget. */
3613     int tindex;			/* Index of element that is to
3614 				 * become the "other" end of the
3615 				 * selection. */
3616 {
3617     int selFirst, selLast;
3618 
3619     /*
3620      * Grab the selection if we don't own it already.
3621      */
3622 
3623     if ((htPtr->exportSelection) && (htPtr->selFirst == -1)) {
3624 	Tk_OwnSelection(htPtr->tkwin, XA_PRIMARY, TextLostSelection, htPtr);
3625     }
3626     /*  If the anchor hasn't been set yet, assume the beginning of the text*/
3627     if (htPtr->selAnchor < 0) {
3628 	htPtr->selAnchor = 0;
3629     }
3630     if (htPtr->selAnchor <= tindex) {
3631 	selFirst = htPtr->selAnchor;
3632 	selLast = tindex;
3633     } else {
3634 	selFirst = tindex;
3635 	selLast = htPtr->selAnchor;
3636     }
3637     if ((htPtr->selFirst != selFirst) || (htPtr->selLast != selLast)) {
3638 	htPtr->selFirst = selFirst, htPtr->selLast = selLast;
3639 	EventuallyRedraw(htPtr);
3640     }
3641     return TCL_OK;
3642 }
3643 
3644 /*
3645  *----------------------------------------------------------------------
3646  *
3647  * SelectOp --
3648  *
3649  *	This procedure handles the individual options for text
3650  *	selections.  The selected text is designated by start and end
3651  *	indices into the text pool.  The selected segment has both a
3652  *	anchored and unanchored ends.  The following selection
3653  *	operations are implemented:
3654  *
3655  *	  "adjust"	- resets either the first or last index
3656  *			  of the selection.
3657  *	  "clear"	- clears the selection. Sets first/last
3658  *			  indices to -1.
3659  *	  "from"	- sets the index of the selection anchor.
3660  *	  "line"	- sets the first of last indices to the
3661  *			  start and end of the line at the
3662  *			  designated point.
3663  *	  "present"	- return "1" if a selection is available,
3664  *			  "0" otherwise.
3665  *	  "range"	- sets the first and last indices.
3666  *	  "to"		- sets the index of the un-anchored end.
3667  *	  "word"	- sets the first of last indices to the
3668  *			  start and end of the word at the
3669  *			  designated point.
3670  * Results:
3671  *	None.
3672  *
3673  * Side effects:
3674  *	The selection changes.
3675  *
3676  *----------------------------------------------------------------------
3677  */
3678 static int
SelectOp(htPtr,interp,argc,argv)3679 SelectOp(htPtr, interp, argc, argv)
3680     HText *htPtr;
3681     Tcl_Interp *interp;
3682     int argc;
3683     char **argv;
3684 {
3685     int iselection;
3686     unsigned int length;
3687     int result = TCL_OK;
3688     char c;
3689 
3690     length = strlen(argv[2]);
3691     c = argv[2][0];
3692     if ((c == 'c') && (strncmp(argv[2], "clear", length) == 0)) {
3693 	if (argc != 3) {
3694 	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
3695 		" selection clear\"", (char *)NULL);
3696 	    return TCL_ERROR;
3697 	}
3698 	if (htPtr->selFirst != -1) {
3699 	    htPtr->selFirst = htPtr->selLast = -1;
3700 	    EventuallyRedraw(htPtr);
3701 	}
3702 	return TCL_OK;
3703     } else if ((c == 'p') && (strncmp(argv[2], "present", length) == 0)) {
3704 	if (argc != 3) {
3705 	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
3706 		" selection present\"", (char *)NULL);
3707 	    return TCL_ERROR;
3708 	}
3709 	Tcl_AppendResult(interp, (htPtr->selFirst != -1) ? "0" : "1",
3710 	    (char *)NULL);
3711 	return TCL_OK;
3712     } else if ((c == 'r') && (strncmp(argv[2], "range", length) == 0)) {
3713 	int selFirst, selLast;
3714 
3715 	if (argc != 5) {
3716 	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
3717 		" selection range first last\"", (char *)NULL);
3718 	    return TCL_ERROR;
3719 	}
3720 	if (GetIndex(htPtr, argv[3], &selFirst) != TCL_OK) {
3721 	    return TCL_ERROR;
3722 	}
3723 	if (GetIndex(htPtr, argv[4], &selLast) != TCL_OK) {
3724 	    return TCL_ERROR;
3725 	}
3726 	htPtr->selAnchor = selFirst;
3727 	SelectTextBlock(htPtr, selLast);
3728 	return TCL_OK;
3729     }
3730     if (argc != 4) {
3731 	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
3732 	    " selection ", argv[2], " index\"", (char *)NULL);
3733 	return TCL_ERROR;
3734     }
3735     if (GetIndex(htPtr, argv[3], &iselection) != TCL_OK) {
3736 	return TCL_ERROR;
3737     }
3738     if ((c == 'f') && (strncmp(argv[2], "from", length) == 0)) {
3739 	htPtr->selAnchor = iselection;
3740     } else if ((c == 'a') && (strncmp(argv[2], "adjust", length) == 0)) {
3741 	int half1, half2;
3742 
3743 	half1 = (htPtr->selFirst + htPtr->selLast) / 2;
3744 	half2 = (htPtr->selFirst + htPtr->selLast + 1) / 2;
3745 	if (iselection < half1) {
3746 	    htPtr->selAnchor = htPtr->selLast;
3747 	} else if (iselection > half2) {
3748 	    htPtr->selAnchor = htPtr->selFirst;
3749 	}
3750 	result = SelectTextBlock(htPtr, iselection);
3751     } else if ((c == 't') && (strncmp(argv[2], "to", length) == 0)) {
3752 	result = SelectTextBlock(htPtr, iselection);
3753     } else if ((c == 'w') && (strncmp(argv[2], "word", length) == 0)) {
3754 	result = SelectWord(htPtr, iselection);
3755     } else if ((c == 'l') && (strncmp(argv[2], "line", length) == 0)) {
3756 	result = SelectLine(htPtr, iselection);
3757     } else {
3758 	Tcl_AppendResult(interp, "bad selection operation \"", argv[2],
3759 	    "\": should be \"adjust\", \"clear\", \"from\", \"line\", \
3760 \"present\", \"range\", \"to\", or \"word\"", (char *)NULL);
3761 	return TCL_ERROR;
3762     }
3763     return result;
3764 }
3765 
3766 /*
3767  *----------------------------------------------------------------------
3768  *
3769  * GotoOp --
3770  *
3771  *	Move the top line of the viewport to the new location based
3772  *	upon the given line number.  Force out-of-range requests to the
3773  *	top or bottom of text.
3774  *
3775  * Results:
3776  *	A standard Tcl result. If TCL_OK, interp->result contains the
3777  *	current line number.
3778  *
3779  * Side effects:
3780  *	At the next idle point, the text viewport will be move to the
3781  *	new line.
3782  *
3783  *----------------------------------------------------------------------
3784  */
3785 /*ARGSUSED*/
3786 static int
GotoOp(htPtr,interp,argc,argv)3787 GotoOp(htPtr, interp, argc, argv)
3788     HText *htPtr;
3789     Tcl_Interp *interp;		/* Not used. */
3790     int argc;
3791     char **argv;
3792 {
3793     int line;
3794 
3795     line = htPtr->first;
3796     if (argc == 3) {
3797 	int tindex;
3798 
3799 	if (GetIndex(htPtr, argv[2], &tindex) != TCL_OK) {
3800 	    return TCL_ERROR;
3801 	}
3802 	line = IndexSearch(htPtr, tindex, 0, htPtr->nLines - 1);
3803 	if (line < 0) {
3804 	    char string[200];
3805 
3806 	    sprintf(string, "can't determine line number from index \"%d\"",
3807 		tindex);
3808 	    Tcl_AppendResult(htPtr->interp, string, (char *)NULL);
3809 	    return TCL_ERROR;
3810 	}
3811 	htPtr->reqLineNum = line;
3812 	htPtr->flags |= TEXT_DIRTY;
3813 
3814 	/*
3815 	 * Make only a request for a change in the viewport.  Defer
3816 	 * the actual scrolling until the text layout is adjusted at
3817 	 * the next idle point.
3818 	 */
3819 	if (line != htPtr->first) {
3820 	    htPtr->flags |= GOTO_PENDING;
3821 	    EventuallyRedraw(htPtr);
3822 	}
3823     }
3824     Tcl_SetResult(htPtr->interp, Blt_Itoa(line), TCL_VOLATILE);
3825     return TCL_OK;
3826 }
3827 
3828 
3829 static int
XViewOp(htPtr,interp,argc,argv)3830 XViewOp(htPtr, interp, argc, argv)
3831     HText *htPtr;
3832     Tcl_Interp *interp;
3833     int argc;
3834     char **argv;
3835 {
3836     int width, worldWidth;
3837 
3838     width = Tk_Width(htPtr->tkwin);
3839     worldWidth = htPtr->worldWidth;
3840     if (argc == 2) {
3841 	double fract;
3842 
3843 	/* Report first and last fractions */
3844 	fract = (double)htPtr->xOffset / worldWidth;
3845 	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
3846 	fract = (double)(htPtr->xOffset + width) / worldWidth;
3847 	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
3848 	return TCL_OK;
3849     }
3850     htPtr->pendingX = htPtr->xOffset;
3851     if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(htPtr->pendingX),
3852 	    worldWidth, width, htPtr->xScrollUnits, BLT_SCROLL_MODE_LISTBOX)
3853 	!= TCL_OK) {
3854 	return TCL_ERROR;
3855     }
3856     htPtr->flags |= TEXT_DIRTY;
3857     EventuallyRedraw(htPtr);
3858     return TCL_OK;
3859 }
3860 
3861 static int
YViewOp(htPtr,interp,argc,argv)3862 YViewOp(htPtr, interp, argc, argv)
3863     HText *htPtr;
3864     Tcl_Interp *interp;
3865     int argc;
3866     char **argv;
3867 {
3868     int height, worldHeight;
3869 
3870     height = Tk_Height(htPtr->tkwin);
3871     worldHeight = htPtr->worldHeight;
3872     if (argc == 2) {
3873 	double fract;
3874 
3875 	/* Report first and last fractions */
3876 	fract = (double)htPtr->yOffset / worldHeight;
3877 	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
3878 	fract = (double)(htPtr->yOffset + height) / worldHeight;
3879 	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
3880 	return TCL_OK;
3881     }
3882     htPtr->pendingY = htPtr->yOffset;
3883     if (Blt_GetScrollInfo(interp, argc - 2, argv + 2, &(htPtr->pendingY),
3884 	    worldHeight, height, htPtr->yScrollUnits, BLT_SCROLL_MODE_LISTBOX)
3885 	!= TCL_OK) {
3886 	return TCL_ERROR;
3887     }
3888     htPtr->flags |= TEXT_DIRTY;
3889     EventuallyRedraw(htPtr);
3890     return TCL_OK;
3891 }
3892 
3893 /*
3894  * ----------------------------------------------------------------------
3895  *
3896  * AppendOp --
3897  *
3898  * 	This procedure embeds a Tk widget into the hypertext.
3899  *
3900  * Results:
3901  *	A standard Tcl result.
3902  *
3903  * Side effects:
3904  *	Memory is allocated.  EmbeddedWidget gets configured.
3905  *
3906  * ----------------------------------------------------------------------
3907  */
3908 static int
AppendOp(htPtr,interp,argc,argv)3909 AppendOp(htPtr, interp, argc, argv)
3910     HText *htPtr;		/* Hypertext widget */
3911     Tcl_Interp *interp;		/* Interpreter associated with widget */
3912     int argc;			/* Number of arguments. */
3913     char **argv;		/* Argument strings. */
3914 {
3915     Line *linePtr;
3916     EmbeddedWidget *winPtr;
3917 
3918     winPtr = CreateEmbeddedWidget(htPtr, argv[2]);
3919     if (winPtr == NULL) {
3920 	return TCL_ERROR;
3921     }
3922     if (Tk_ConfigureWidget(interp, htPtr->tkwin, widgetConfigSpecs,
3923 	    argc - 3, argv + 3, (char *)winPtr, 0) != TCL_OK) {
3924 	return TCL_ERROR;
3925     }
3926     /*
3927      * Append widget to list of embedded widgets of the last line.
3928      */
3929     linePtr = GetLastLine(htPtr);
3930     if (linePtr == NULL) {
3931 	Tcl_AppendResult(htPtr->interp, "can't allocate line structure",
3932 	    (char *)NULL);
3933 	return TCL_ERROR;
3934     }
3935     Blt_ChainAppend(linePtr->chainPtr, winPtr);
3936     linePtr->width += winPtr->cavityWidth;
3937     winPtr->precedingTextEnd = linePtr->textEnd;
3938 
3939     htPtr->flags |= (WIDGET_APPENDED | REQUEST_LAYOUT);
3940     EventuallyRedraw(htPtr);
3941     return TCL_OK;
3942 }
3943 
3944 /*
3945  *----------------------------------------------------------------------
3946  *
3947  * WindowsOp --
3948  *
3949  *	Returns a list of all the pathNames of embedded widgets of the
3950  *	HText widget.  If a pattern argument is given, only the names
3951  *	of windows matching it will be placed into the list.
3952  *
3953  * Results:
3954  *	Standard Tcl result.  If TCL_OK, interp->result will contain
3955  *	the list of the embedded widget pathnames.  Otherwise it will
3956  *	contain an error message.
3957  *
3958  *----------------------------------------------------------------------
3959  */
3960 static int
WindowsOp(htPtr,interp,argc,argv)3961 WindowsOp(htPtr, interp, argc, argv)
3962     HText *htPtr;		/* Hypertext widget record */
3963     Tcl_Interp *interp;		/* Interpreter associated with widget */
3964     int argc;
3965     char **argv;
3966 {
3967     EmbeddedWidget *winPtr;
3968     Blt_HashEntry *hPtr;
3969     Blt_HashSearch cursor;
3970     char *name;
3971 
3972     for (hPtr = Blt_FirstHashEntry(&(htPtr->widgetTable), &cursor);
3973 	hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
3974 	winPtr = (EmbeddedWidget *)Blt_GetHashValue(hPtr);
3975 	if (winPtr->tkwin == NULL) {
3976 	    fprintf(stderr, "window `%s' is null\n",
3977 		Tk_PathName(Blt_GetHashKey(&(htPtr->widgetTable), hPtr)));
3978 	    continue;
3979 	}
3980 	name = Tk_PathName(winPtr->tkwin);
3981 	if ((argc == 2) || (Tcl_StringMatch(name, argv[2]))) {
3982 	    Tcl_AppendElement(interp, name);
3983 	}
3984     }
3985     return TCL_OK;
3986 }
3987 
3988 /*
3989  *----------------------------------------------------------------------
3990  *
3991  * CgetOp --
3992  *
3993  *----------------------------------------------------------------------
3994  */
3995 /*ARGSUSED*/
3996 static int
CgetOp(htPtr,interp,argc,argv)3997 CgetOp(htPtr, interp, argc, argv)
3998     HText *htPtr;
3999     Tcl_Interp *interp;
4000     int argc;			/* Not used. */
4001     char **argv;
4002 {
4003     char *itemPtr;
4004     Tk_ConfigSpec *specsPtr;
4005 
4006     if ((argc > 2) && (argv[2][0] == '.')) {
4007 	Tk_Window tkwin;
4008 	EmbeddedWidget *winPtr;
4009 
4010 	/* EmbeddedWidget window to be configured */
4011 	tkwin = Tk_NameToWindow(interp, argv[2], htPtr->tkwin);
4012 	if (tkwin == NULL) {
4013 	    return TCL_ERROR;
4014 	}
4015 	winPtr = FindEmbeddedWidget(htPtr, tkwin);
4016 	if (winPtr == NULL) {
4017 	    Tcl_AppendResult(interp, "window \"", argv[2],
4018 		"\" is not managed by \"", argv[0], "\"", (char *)NULL);
4019 	    return TCL_ERROR;
4020 	}
4021 	specsPtr = widgetConfigSpecs;
4022 	itemPtr = (char *)winPtr;
4023 	argv++;
4024     } else {
4025 	specsPtr = configSpecs;
4026 	itemPtr = (char *)htPtr;
4027     }
4028     return Tk_ConfigureValue(interp, htPtr->tkwin, specsPtr, itemPtr,
4029 	    argv[2], 0);
4030 }
4031 
4032 /*
4033  *----------------------------------------------------------------------
4034  *
4035  * ConfigureOp --
4036  *
4037  * 	This procedure is called to process an argv/argc list, plus
4038  *	the Tk option database, in order to configure (or reconfigure)
4039  *	a hypertext widget.
4040  *
4041  * Results:
4042  *	A standard Tcl result.  If TCL_ERROR is returned, then
4043  *	interp->result contains an error message.
4044  *
4045  * Side effects:
4046  *	Configuration information, such as text string, colors, font,
4047  * 	etc. get set for htPtr;  old resources get freed, if there were any.
4048  * 	The hypertext is redisplayed.
4049  *
4050  *----------------------------------------------------------------------
4051  */
4052 static int
ConfigureOp(htPtr,interp,argc,argv)4053 ConfigureOp(htPtr, interp, argc, argv)
4054     HText *htPtr;
4055     Tcl_Interp *interp;
4056     int argc;
4057     char **argv;
4058 {
4059     char *itemPtr;
4060     Tk_ConfigSpec *specsPtr;
4061 
4062     if ((argc > 2) && (argv[2][0] == '.')) {
4063 	Tk_Window tkwin;
4064 	EmbeddedWidget *winPtr;
4065 
4066 	/* EmbeddedWidget window to be configured */
4067 	tkwin = Tk_NameToWindow(interp, argv[2], htPtr->tkwin);
4068 	if (tkwin == NULL) {
4069 	    return TCL_ERROR;
4070 	}
4071 	winPtr = FindEmbeddedWidget(htPtr, tkwin);
4072 	if (winPtr == NULL) {
4073 	    Tcl_AppendResult(interp, "window \"", argv[2],
4074 		"\" is not managed by \"", argv[0], "\"", (char *)NULL);
4075 	    return TCL_ERROR;
4076 	}
4077 	specsPtr = widgetConfigSpecs;
4078 	itemPtr = (char *)winPtr;
4079 	argv++;
4080 	argc--;
4081     } else {
4082 	specsPtr = configSpecs;
4083 	itemPtr = (char *)htPtr;
4084     }
4085     if (argc == 2) {
4086 	return Tk_ConfigureInfo(interp, htPtr->tkwin, specsPtr, itemPtr,
4087 		(char *)NULL, 0);
4088     } else if (argc == 3) {
4089 	return Tk_ConfigureInfo(interp, htPtr->tkwin, specsPtr, itemPtr,
4090 		argv[2], 0);
4091     }
4092     if (Tk_ConfigureWidget(interp, htPtr->tkwin, specsPtr, argc - 2,
4093 	    argv + 2, itemPtr, TK_CONFIG_ARGV_ONLY) != TCL_OK) {
4094 	return TCL_ERROR;
4095     }
4096     if (itemPtr == (char *)htPtr) {
4097 	/* Reconfigure the master */
4098 	if (ConfigureText(interp, htPtr) != TCL_OK) {
4099 	    return TCL_ERROR;
4100 	}
4101     } else {
4102 	htPtr->flags |= REQUEST_LAYOUT;
4103     }
4104     EventuallyRedraw(htPtr);
4105     return TCL_OK;
4106 }
4107 
4108 /*
4109  *----------------------------------------------------------------------
4110  *
4111  * ScanOp --
4112  *
4113  *	Implements the quick scan for hypertext widgets.
4114  *
4115  *----------------------------------------------------------------------
4116  */
4117 /*ARGSUSED*/
4118 static int
ScanOp(htPtr,interp,argc,argv)4119 ScanOp(htPtr, interp, argc, argv)
4120     HText *htPtr;
4121     Tcl_Interp *interp;
4122     int argc;			/* Not used. */
4123     char **argv;
4124 {
4125     int x, y;
4126     char c;
4127     unsigned int length;
4128 
4129 
4130     if (Blt_GetXY(interp, htPtr->tkwin, argv[3], &x, &y) != TCL_OK) {
4131 	return TCL_ERROR;
4132     }
4133     c = argv[2][0];
4134     length = strlen(argv[2]);
4135     if ((c == 'm') && (strncmp(argv[2], "mark", length) == 0)) {
4136 	htPtr->scanMark.x = x, htPtr->scanMark.y = y;
4137 	htPtr->scanPt.x = htPtr->xOffset;
4138 	htPtr->scanPt.y = htPtr->yOffset;
4139 
4140     } else if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
4141 	int px, py;
4142 
4143 	px = htPtr->scanPt.x - (10 * (x - htPtr->scanMark.x));
4144 	py = htPtr->scanPt.y - (10 * (y - htPtr->scanMark.y));
4145 
4146 	if (px < 0) {
4147 	    px = htPtr->scanPt.x = 0;
4148 	    htPtr->scanMark.x = x;
4149 	} else if (px >= htPtr->worldWidth) {
4150 	    px = htPtr->scanPt.x = htPtr->worldWidth - htPtr->xScrollUnits;
4151 	    htPtr->scanMark.x = x;
4152 	}
4153 	if (py < 0) {
4154 	    py = htPtr->scanPt.y = 0;
4155 	    htPtr->scanMark.y = y;
4156 	} else if (py >= htPtr->worldHeight) {
4157 	    py = htPtr->scanPt.y = htPtr->worldHeight - htPtr->yScrollUnits;
4158 	    htPtr->scanMark.y = y;
4159 	}
4160 	if ((py != htPtr->pendingY) || (px != htPtr->pendingX)) {
4161 	    htPtr->pendingX = px, htPtr->pendingY = py;
4162 	    htPtr->flags |= TEXT_DIRTY;
4163 	    EventuallyRedraw(htPtr);
4164 	}
4165     } else {
4166 	Tcl_AppendResult(interp, "bad scan operation \"", argv[2],
4167 	    "\": should be either \"mark\" or \"dragto\"", (char *)NULL);
4168 	return TCL_ERROR;
4169     }
4170     return TCL_OK;
4171 }
4172 
4173 /*
4174  *----------------------------------------------------------------------
4175  *
4176  * SearchOp --
4177  *
4178  *	Returns the linenumber of the next line matching the given
4179  *	pattern within the range of lines provided.  If the first
4180  *	line number is greater than the last, the search is done in
4181  *	reverse.
4182  *
4183  *----------------------------------------------------------------------
4184  */
4185 static int
SearchOp(htPtr,interp,argc,argv)4186 SearchOp(htPtr, interp, argc, argv)
4187     HText *htPtr;
4188     Tcl_Interp *interp;
4189     int argc;
4190     char **argv;
4191 {
4192     char *startPtr, *endPtr;
4193     char saved;
4194     Tcl_RegExp regExpToken;
4195     int iFirst, iLast;
4196     int matchStart, matchEnd;
4197     int match;
4198 
4199     regExpToken = Tcl_RegExpCompile(interp, argv[2]);
4200     if (regExpToken == NULL) {
4201 	return TCL_ERROR;
4202     }
4203     iFirst = 0;
4204     iLast = htPtr->nChars;
4205     if (argc > 3) {
4206 	if (GetIndex(htPtr, argv[3], &iFirst) != TCL_OK) {
4207 	    return TCL_ERROR;
4208 	}
4209     }
4210     if (argc == 4) {
4211 	if (GetIndex(htPtr, argv[4], &iLast) != TCL_OK) {
4212 	    return TCL_ERROR;
4213 	}
4214     }
4215     if (iLast < iFirst) {
4216 	return TCL_ERROR;
4217     }
4218     matchStart = matchEnd = -1;
4219     startPtr = htPtr->charArr + iFirst;
4220     endPtr = htPtr->charArr + (iLast + 1);
4221     saved = *endPtr;
4222     *endPtr = '\0';		/* Make the line a string by changing the
4223 				 * '\n' into a NUL byte before searching */
4224     match = Tcl_RegExpExec(interp, regExpToken, startPtr, startPtr);
4225     *endPtr = saved;
4226     if (match < 0) {
4227 	return TCL_ERROR;
4228     } else if (match > 0) {
4229 	Tcl_RegExpRange(regExpToken, 0, &startPtr, &endPtr);
4230 	if ((startPtr != NULL) || (endPtr != NULL)) {
4231 	    matchStart = startPtr - htPtr->charArr;
4232 	    matchEnd = endPtr - htPtr->charArr - 1;
4233 	}
4234     }
4235     if (match > 0) {
4236 	Tcl_AppendElement(interp, Blt_Itoa(matchStart));
4237 	Tcl_AppendElement(interp, Blt_Itoa(matchEnd));
4238     } else {
4239 	Tcl_ResetResult(interp);
4240     }
4241     return TCL_OK;
4242 }
4243 
4244 /*
4245  *----------------------------------------------------------------------
4246  *
4247  * RangeOp --
4248  *
4249  *	Returns the characters designated by the range of elements.
4250  *
4251  *----------------------------------------------------------------------
4252  */
4253 static int
RangeOp(htPtr,interp,argc,argv)4254 RangeOp(htPtr, interp, argc, argv)
4255     HText *htPtr;
4256     Tcl_Interp *interp;
4257     int argc;
4258     char **argv;
4259 {
4260     char *startPtr, *endPtr;
4261     char saved;
4262     int textFirst, textLast;
4263 
4264     textFirst = htPtr->selFirst;
4265     textLast = htPtr->selLast;
4266     if (textFirst < 0) {
4267 	textFirst = 0;
4268 	textLast = htPtr->nChars - 1;
4269     }
4270     if (argc > 2) {
4271 	if (GetIndex(htPtr, argv[2], &textFirst) != TCL_OK) {
4272 	    return TCL_ERROR;
4273 	}
4274     }
4275     if (argc == 4) {
4276 	if (GetIndex(htPtr, argv[3], &textLast) != TCL_OK) {
4277 	    return TCL_ERROR;
4278 	}
4279     }
4280     if (textLast < textFirst) {
4281 	Tcl_AppendResult(interp, "first index is greater than last", (char *)NULL);
4282 	return TCL_ERROR;
4283     }
4284     startPtr = htPtr->charArr + textFirst;
4285     endPtr = htPtr->charArr + (textLast + 1);
4286     saved = *endPtr;
4287     *endPtr = '\0';		/* Make the line into a string by
4288 				 * changing the * '\n' into a '\0'
4289 				 * before copying */
4290     Tcl_SetResult(interp, startPtr, TCL_VOLATILE);
4291     *endPtr = saved;
4292     return TCL_OK;
4293 }
4294 
4295 /*
4296  *----------------------------------------------------------------------
4297  *
4298  * IndexOp --
4299  *
4300  *----------------------------------------------------------------------
4301  */
4302 /*ARGSUSED*/
4303 static int
IndexOp(htPtr,interp,argc,argv)4304 IndexOp(htPtr, interp, argc, argv)
4305     HText *htPtr;
4306     Tcl_Interp *interp;
4307     int argc;			/* Not used. */
4308     char **argv;
4309 {
4310     int tindex;
4311 
4312     if (GetIndex(htPtr, argv[2], &tindex) != TCL_OK) {
4313 	return TCL_ERROR;
4314     }
4315     Tcl_SetResult(interp, Blt_Itoa(tindex), TCL_VOLATILE);
4316     return TCL_OK;
4317 }
4318 
4319 /*
4320  *----------------------------------------------------------------------
4321  *
4322  * LinePosOp --
4323  *
4324  *----------------------------------------------------------------------
4325  */
4326 /*ARGSUSED*/
4327 static int
LinePosOp(htPtr,interp,argc,argv)4328 LinePosOp(htPtr, interp, argc, argv)
4329     HText *htPtr;
4330     Tcl_Interp *interp;
4331     int argc;			/* Not used. */
4332     char **argv;
4333 {
4334     int line, cpos, tindex;
4335     char string[200];
4336 
4337     if (GetIndex(htPtr, argv[2], &tindex) != TCL_OK) {
4338 	return TCL_ERROR;
4339     }
4340     if (GetTextPosition(htPtr, tindex, &line, &cpos) != TCL_OK) {
4341 	return TCL_ERROR;
4342     }
4343     sprintf(string, "%d.%d", line, cpos);
4344     Tcl_SetResult(interp, string, TCL_VOLATILE);
4345     return TCL_OK;
4346 }
4347 
4348 /*
4349  * --------------------------------------------------------------
4350  *
4351  * TextWidgetCmd --
4352  *
4353  * 	This procedure is invoked to process the Tcl command that
4354  *	corresponds to a widget managed by this module. See the user
4355  * 	documentation for details on what it does.
4356  *
4357  * Results:
4358  *	A standard Tcl result.
4359  *
4360  * Side effects:
4361  *	See the user documentation.
4362  *
4363  * --------------------------------------------------------------
4364  */
4365 
4366 static Blt_OpSpec textOps[] =
4367 {
4368     {"append", 1, (Blt_Op)AppendOp, 3, 0, "window ?option value?...",},
4369     {"cget", 2, (Blt_Op)CgetOp, 3, 3, "?window? option",},
4370     {"configure", 2, (Blt_Op)ConfigureOp, 2, 0,
4371 	"?window? ?option value?...",},
4372     {"gotoline", 2, (Blt_Op)GotoOp, 2, 3, "?line?",},
4373     {"index", 1, (Blt_Op)IndexOp, 3, 3, "string",},
4374     {"linepos", 1, (Blt_Op)LinePosOp, 3, 3, "string",},
4375     {"range", 2, (Blt_Op)RangeOp, 2, 4, "?from? ?to?",},
4376     {"scan", 2, (Blt_Op)ScanOp, 4, 4, "oper @x,y",},
4377     {"search", 3, (Blt_Op)SearchOp, 3, 5, "pattern ?from? ?to?",},
4378     {"selection", 3, (Blt_Op)SelectOp, 3, 5, "oper ?index?",},
4379     {"windows", 6, (Blt_Op)WindowsOp, 2, 3, "?pattern?",},
4380     {"xview", 1, (Blt_Op)XViewOp, 2, 5,
4381 	"?moveto fract? ?scroll number what?",},
4382     {"yview", 1, (Blt_Op)YViewOp, 2, 5,
4383 	"?moveto fract? ?scroll number what?",},
4384 };
4385 static int nTextOps = sizeof(textOps) / sizeof(Blt_OpSpec);
4386 
4387 static int
TextWidgetCmd(clientData,interp,argc,argv)4388 TextWidgetCmd(clientData, interp, argc, argv)
4389     ClientData clientData;	/* Information about hypertext widget. */
4390     Tcl_Interp *interp;		/* Current interpreter. */
4391     int argc;			/* Number of arguments. */
4392     char **argv;		/* Argument strings. */
4393 {
4394     Blt_Op proc;
4395     int result;
4396     HText *htPtr = clientData;
4397 
4398     proc = Blt_GetOp(interp, nTextOps, textOps, BLT_OP_ARG1, argc, argv, 0);
4399     if (proc == NULL) {
4400 	return TCL_ERROR;
4401     }
4402     Tcl_Preserve(htPtr);
4403     result = (*proc) (htPtr, interp, argc, argv);
4404     Tcl_Release(htPtr);
4405     return result;
4406 }
4407 
4408 /*
4409  * --------------------------------------------------------------
4410  *
4411  * TextCmd --
4412  *
4413  * 	This procedure is invoked to process the "htext" Tcl command.
4414  *	See the user documentation for details on what it does.
4415  *
4416  * Results:
4417  *	A standard Tcl result.
4418  *
4419  * Side effects:
4420  *	See the user documentation.
4421  *
4422  * --------------------------------------------------------------
4423  */
4424 /* ARGSUSED */
4425 static int
TextCmd(clientData,interp,argc,argv)4426 TextCmd(clientData, interp, argc, argv)
4427     ClientData clientData;	/* Main window associated with interpreter. */
4428     Tcl_Interp *interp;		/* Current interpreter. */
4429     int argc;			/* Number of arguments. */
4430     char **argv;		/* Argument strings. */
4431 {
4432     HText *htPtr;
4433     Screen *screenPtr;
4434     Tk_Window tkwin;
4435 
4436     if (argc < 2) {
4437 	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
4438 	    " pathName ?option value?...\"", (char *)NULL);
4439 	return TCL_ERROR;
4440     }
4441     htPtr = Blt_Calloc(1, sizeof(HText));
4442     assert(htPtr);
4443     tkwin = Tk_MainWindow(interp);
4444     tkwin = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *)NULL);
4445     if (tkwin == NULL) {
4446 	Blt_Free(htPtr);
4447 	return TCL_ERROR;
4448     }
4449     /* Initialize the new hypertext widget */
4450 
4451     Tk_SetClass(tkwin, "Htext");
4452     htPtr->tkwin = tkwin;
4453     htPtr->display = Tk_Display(tkwin);
4454     htPtr->interp = interp;
4455     htPtr->nLines = htPtr->arraySize = 0;
4456     htPtr->leader = 1;
4457     htPtr->xScrollUnits = htPtr->yScrollUnits = 10;
4458     htPtr->nRows = htPtr->nColumns = 0;
4459     htPtr->selFirst = htPtr->selLast = -1;
4460     htPtr->selAnchor = 0;
4461     htPtr->exportSelection = TRUE;
4462     htPtr->selBorderWidth = 2;
4463     screenPtr = Tk_Screen(htPtr->tkwin);
4464     htPtr->maxWidth = WidthOfScreen(screenPtr);
4465     htPtr->maxHeight = HeightOfScreen(screenPtr);
4466     Blt_InitHashTable(&(htPtr->widgetTable), BLT_ONE_WORD_KEYS);
4467 
4468     Tk_CreateSelHandler(tkwin, XA_PRIMARY, XA_STRING, TextSelectionProc,
4469 	htPtr, XA_STRING);
4470     Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask,
4471 	TextEventProc, htPtr);
4472 #if (TK_MAJOR_VERSION > 4)
4473     Blt_SetWindowInstanceData(tkwin, htPtr);
4474 #endif
4475     /*
4476      * -----------------------------------------------------------------
4477      *
4478      *  Create the widget command before configuring the widget. This
4479      *  is because the "-file" and "-text" options may have embedded
4480      *  commands that self-reference the widget through the
4481      *  "$blt_htext(widget)" variable.
4482      *
4483      * ------------------------------------------------------------------
4484      */
4485     htPtr->cmdToken = Tcl_CreateCommand(interp, argv[1], TextWidgetCmd, htPtr,
4486 	TextDeleteCmdProc);
4487 #ifdef ITCL_NAMESPACES
4488     Itk_SetWidgetCommand(htPtr->tkwin, htPtr->cmdToken);
4489 #endif
4490     if ((Tk_ConfigureWidget(interp, htPtr->tkwin, configSpecs, argc - 2,
4491 		argv + 2, (char *)htPtr, 0) != TCL_OK) ||
4492 	(ConfigureText(interp, htPtr) != TCL_OK)) {
4493 	Tk_DestroyWindow(htPtr->tkwin);
4494 	return TCL_ERROR;
4495     }
4496     Tcl_SetResult(interp, Tk_PathName(htPtr->tkwin), TCL_VOLATILE);
4497     Tk_SetClassProcs(tkwin, &htextClass, (ClientData)htPtr);
4498     return TCL_OK;
4499 }
4500 
4501 int
Blt_HtextInit(interp)4502 Blt_HtextInit(interp)
4503     Tcl_Interp *interp;
4504 {
4505     static Blt_CmdSpec cmdSpec =
4506     {"htext", TextCmd,};
4507 
4508     if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) {
4509 	return TCL_ERROR;
4510     }
4511     return TCL_OK;
4512 }
4513 
4514 #endif /* NO_HTEXT */
4515