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