1/*
2 * tkTable.c --
3 *
4 *	This module implements table widgets for the Tk
5 *	toolkit.  An table displays a 2D array of strings
6 *	and allows the strings to be edited.
7 *
8 * Based on Tk3 table widget written by Roland King
9 *
10 * Updates 1996 by:
11 * Jeffrey Hobbs	jeff at hobbs org
12 * John Ellson		ellson@lucent.com
13 * Peter Bruecker	peter@bj-ig.de
14 * Tom Moore		tmoore@spatial.ca
15 * Sebastian Wangnick	wangnick@orthogon.de
16 *
17 * Copyright (c) 1997-2002 Jeffrey Hobbs
18 *
19 * See the file "license.txt" for information on usage and redistribution
20 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
21 *
22 * RCS: @(#) $Id: tkTable.c,v 1.35 2010/08/05 20:41:57 hobbs Exp $
23 */
24
25#include "tkTable.h"
26
27#ifdef DEBUG
28#include "dprint.h"
29#endif
30
31static char **	StringifyObjects(int objc, Tcl_Obj *CONST objv[]);
32
33static int	Tk_TableObjCmd(ClientData clientData, Tcl_Interp *interp,
34			int objc, Tcl_Obj *CONST objv[]);
35
36static int	TableWidgetObjCmd(ClientData clientData, Tcl_Interp *interp,
37			int objc, Tcl_Obj *CONST objv[]);
38static int	TableConfigure(Tcl_Interp *interp, Table *tablePtr,
39			int objc, Tcl_Obj *CONST objv[],
40			int flags, int forceUpdate);
41#ifdef HAVE_TCL84
42static void	TableWorldChanged(ClientData instanceData);
43#endif
44static void	TableDestroy(ClientData clientdata);
45static void	TableEventProc(ClientData clientData, XEvent *eventPtr);
46static void	TableCmdDeletedProc(ClientData clientData);
47
48static void	TableRedrawHighlight(Table *tablePtr);
49static void	TableGetGc(Display *display, Drawable d,
50			TableTag *tagPtr, GC *tagGc);
51
52static void	TableDisplay(ClientData clientdata);
53static void	TableFlashEvent(ClientData clientdata);
54static char *	TableVarProc(ClientData clientData, Tcl_Interp *interp,
55			char *name, char *index, int flags);
56static void	TableCursorEvent(ClientData clientData);
57static int	TableFetchSelection(ClientData clientData,
58			int offset, char *buffer, int maxBytes);
59static Tk_RestrictAction TableRestrictProc(ClientData arg, XEvent *eventPtr);
60
61/*
62 * The following tables define the widget commands (and sub-
63 * commands) and map the indexes into the string tables into
64 * enumerated types used to dispatch the widget command.
65 */
66
67static CONST84 char *selCmdNames[] = {
68    "anchor", "clear", "includes", "present", "set", (char *)NULL
69};
70enum selCommand {
71    CMD_SEL_ANCHOR, CMD_SEL_CLEAR, CMD_SEL_INCLUDES, CMD_SEL_PRESENT,
72    CMD_SEL_SET
73};
74
75static CONST84 char *commandNames[] = {
76    "activate", "bbox", "border", "cget", "clear", "configure",
77    "curselection", "curvalue", "delete", "get", "height",
78    "hidden", "icursor", "index", "insert",
79#ifdef POSTSCRIPT
80    "postscript",
81#endif
82    "reread", "scan", "see", "selection", "set",
83    "spans", "tag", "validate", "version", "window", "width",
84    "xview", "yview", (char *)NULL
85};
86enum command {
87    CMD_ACTIVATE, CMD_BBOX, CMD_BORDER, CMD_CGET, CMD_CLEAR, CMD_CONFIGURE,
88    CMD_CURSEL, CMD_CURVALUE, CMD_DELETE, CMD_GET, CMD_HEIGHT,
89    CMD_HIDDEN, CMD_ICURSOR, CMD_INDEX, CMD_INSERT,
90#ifdef POSTSCRIPT
91    CMD_POSTSCRIPT,
92#endif
93    CMD_REREAD, CMD_SCAN, CMD_SEE, CMD_SELECTION, CMD_SET,
94    CMD_SPANS, CMD_TAG, CMD_VALIDATE, CMD_VERSION, CMD_WINDOW, CMD_WIDTH,
95    CMD_XVIEW, CMD_YVIEW
96};
97
98/* -selecttype selection type options */
99static Cmd_Struct sel_vals[]= {
100    {"row",	 SEL_ROW},
101    {"col",	 SEL_COL},
102    {"both",	 SEL_BOTH},
103    {"cell",	 SEL_CELL},
104    {"",	 0 }
105};
106
107/* -resizeborders options */
108static Cmd_Struct resize_vals[]= {
109    {"row",	 SEL_ROW},		/* allow rows to be dragged */
110    {"col",	 SEL_COL},		/* allow cols to be dragged */
111    {"both",	 SEL_ROW|SEL_COL},	/* allow either to be dragged */
112    {"none",	 SEL_NONE},		/* allow nothing to be dragged */
113    {"",	 0 }
114};
115
116/* drawmode values */
117/* The display redraws with a pixmap using TK function calls */
118#define	DRAW_MODE_SLOW		(1<<0)
119/* The redisplay is direct to the screen, but TK function calls are still
120 * used to give correct 3-d border appearance and thus remain compatible
121 * with other TK apps */
122#define	DRAW_MODE_TK_COMPAT	(1<<1)
123/* the redisplay goes straight to the screen and the 3d borders are rendered
124 * with a single pixel wide line only. It cheats and uses the internal
125 * border structure to do the borders */
126#define DRAW_MODE_FAST		(1<<2)
127#define DRAW_MODE_SINGLE	(1<<3)
128
129static Cmd_Struct drawmode_vals[] = {
130    {"fast",		DRAW_MODE_FAST},
131    {"compatible",	DRAW_MODE_TK_COMPAT},
132    {"slow",		DRAW_MODE_SLOW},
133    {"single",		DRAW_MODE_SINGLE},
134    {"", 0}
135};
136
137/* stretchmode values */
138#define	STRETCH_MODE_NONE	(1<<0)	/* No additional pixels will be
139					   added to rows or cols */
140#define	STRETCH_MODE_UNSET	(1<<1)	/* All default rows or columns will
141					   be stretched to fill the screen */
142#define STRETCH_MODE_ALL	(1<<2)	/* All rows/columns will be padded
143					   to fill the window */
144#define STRETCH_MODE_LAST	(1<<3)	/* Stretch last elememt to fill
145					   window */
146#define STRETCH_MODE_FILL       (1<<4)	/* More ROWS in Window */
147
148static Cmd_Struct stretch_vals[] = {
149    {"none",	STRETCH_MODE_NONE},
150    {"unset",	STRETCH_MODE_UNSET},
151    {"all",	STRETCH_MODE_ALL},
152    {"last",	STRETCH_MODE_LAST},
153    {"fill",	STRETCH_MODE_FILL},
154    {"", 0}
155};
156
157static Cmd_Struct state_vals[]= {
158    {"normal",	 STATE_NORMAL},
159    {"disabled", STATE_DISABLED},
160    {"",	 0 }
161};
162
163/* The widget configuration table */
164static Tk_CustomOption drawOpt		= { Cmd_OptionSet, Cmd_OptionGet,
165					    (ClientData)(&drawmode_vals) };
166static Tk_CustomOption resizeTypeOpt	= { Cmd_OptionSet, Cmd_OptionGet,
167					    (ClientData)(&resize_vals) };
168static Tk_CustomOption stretchOpt	= { Cmd_OptionSet, Cmd_OptionGet,
169					    (ClientData)(&stretch_vals) };
170static Tk_CustomOption selTypeOpt	= { Cmd_OptionSet, Cmd_OptionGet,
171					    (ClientData)(&sel_vals) };
172static Tk_CustomOption stateTypeOpt	= { Cmd_OptionSet, Cmd_OptionGet,
173					    (ClientData)(&state_vals) };
174static Tk_CustomOption bdOpt		= { TableOptionBdSet, TableOptionBdGet,
175					    (ClientData) BD_TABLE };
176
177Tk_ConfigSpec tableSpecs[] = {
178    {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", "center",
179     Tk_Offset(Table, defaultTag.anchor), 0},
180    {TK_CONFIG_BOOLEAN, "-autoclear", "autoClear", "AutoClear", "0",
181     Tk_Offset(Table, autoClear), 0},
182    {TK_CONFIG_BORDER, "-background", "background", "Background", NORMAL_BG,
183     Tk_Offset(Table, defaultTag.bg), 0},
184    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0},
185    {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
186    {TK_CONFIG_CURSOR, "-bordercursor", "borderCursor", "Cursor", "crosshair",
187     Tk_Offset(Table, bdcursor), TK_CONFIG_NULL_OK },
188    {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", "1",
189     Tk_Offset(Table, defaultTag), TK_CONFIG_NULL_OK, &bdOpt },
190    {TK_CONFIG_STRING, "-browsecommand", "browseCommand", "BrowseCommand", "",
191     Tk_Offset(Table, browseCmd), TK_CONFIG_NULL_OK},
192    {TK_CONFIG_SYNONYM, "-browsecmd", "browseCommand", (char *)NULL,
193     (char *)NULL, 0, TK_CONFIG_NULL_OK},
194    {TK_CONFIG_BOOLEAN, "-cache", "cache", "Cache", "0",
195     Tk_Offset(Table, caching), 0},
196    {TK_CONFIG_INT, "-colorigin", "colOrigin", "Origin", "0",
197     Tk_Offset(Table, colOffset), 0},
198    {TK_CONFIG_INT, "-cols", "cols", "Cols", "10",
199     Tk_Offset(Table, cols), 0},
200    {TK_CONFIG_STRING, "-colseparator", "colSeparator", "Separator", NULL,
201     Tk_Offset(Table, colSep), TK_CONFIG_NULL_OK },
202    {TK_CONFIG_CUSTOM, "-colstretchmode", "colStretch", "StretchMode", "none",
203     Tk_Offset (Table, colStretch), 0 , &stretchOpt },
204    {TK_CONFIG_STRING, "-coltagcommand", "colTagCommand", "TagCommand", NULL,
205     Tk_Offset(Table, colTagCmd), TK_CONFIG_NULL_OK },
206    {TK_CONFIG_INT, "-colwidth", "colWidth", "ColWidth", "10",
207     Tk_Offset(Table, defColWidth), 0},
208    {TK_CONFIG_STRING, "-command", "command", "Command", "",
209     Tk_Offset(Table, command), TK_CONFIG_NULL_OK},
210    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", "xterm",
211     Tk_Offset(Table, cursor), TK_CONFIG_NULL_OK },
212    {TK_CONFIG_CUSTOM, "-drawmode", "drawMode", "DrawMode", "compatible",
213     Tk_Offset(Table, drawMode), 0, &drawOpt },
214    {TK_CONFIG_STRING, "-ellipsis", "ellipsis", "Ellipsis", "",
215     Tk_Offset(Table, defaultTag.ellipsis), TK_CONFIG_NULL_OK},
216    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
217     "ExportSelection", "1", Tk_Offset(Table, exportSelection), 0},
218    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0},
219    {TK_CONFIG_BOOLEAN, "-flashmode", "flashMode", "FlashMode", "0",
220     Tk_Offset(Table, flashMode), 0},
221    {TK_CONFIG_INT, "-flashtime", "flashTime", "FlashTime", "2",
222     Tk_Offset(Table, flashTime), 0},
223    {TK_CONFIG_FONT, "-font", "font", "Font",  DEF_TABLE_FONT,
224     Tk_Offset(Table, defaultTag.tkfont), 0},
225    {TK_CONFIG_BORDER, "-foreground", "foreground", "Foreground", "black",
226     Tk_Offset(Table, defaultTag.fg), 0},
227#ifdef PROCS
228    {TK_CONFIG_BOOLEAN, "-hasprocs", "hasProcs", "hasProcs", "0",
229     Tk_Offset(Table, hasProcs), 0},
230#endif
231    {TK_CONFIG_INT, "-height", "height", "Height", "0",
232     Tk_Offset(Table, maxReqRows), 0},
233    {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground",
234     "HighlightBackground", NORMAL_BG, Tk_Offset(Table, highlightBgColorPtr), 0},
235    {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
236     HIGHLIGHT, Tk_Offset(Table, highlightColorPtr), 0},
237    {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness",
238     "HighlightThickness", "2", Tk_Offset(Table, highlightWidth), 0},
239    {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground",
240     "Black", Tk_Offset(Table, insertBg), 0},
241    {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
242     "0", Tk_Offset(Table, insertBorderWidth), TK_CONFIG_COLOR_ONLY},
243    {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
244     "0", Tk_Offset(Table, insertBorderWidth), TK_CONFIG_MONO_ONLY},
245    {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime", "300",
246     Tk_Offset(Table, insertOffTime), 0},
247    {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime", "600",
248     Tk_Offset(Table, insertOnTime), 0},
249    {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", "2",
250     Tk_Offset(Table, insertWidth), 0},
251    {TK_CONFIG_BOOLEAN, "-invertselected", "invertSelected", "InvertSelected",
252     "0", Tk_Offset(Table, invertSelected), 0},
253    {TK_CONFIG_PIXELS, "-ipadx", "ipadX", "Pad", "0",
254     Tk_Offset(Table, ipadX), 0},
255    {TK_CONFIG_PIXELS, "-ipady", "ipadY", "Pad", "0",
256     Tk_Offset(Table, ipadY), 0},
257    {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify", "left",
258     Tk_Offset(Table, defaultTag.justify), 0 },
259    {TK_CONFIG_PIXELS, "-maxheight", "maxHeight", "MaxHeight", "600",
260     Tk_Offset(Table, maxReqHeight), 0},
261    {TK_CONFIG_PIXELS, "-maxwidth", "maxWidth", "MaxWidth", "800",
262     Tk_Offset(Table, maxReqWidth), 0},
263    {TK_CONFIG_BOOLEAN, "-multiline", "multiline", "Multiline", "1",
264     Tk_Offset(Table, defaultTag.multiline), 0},
265    {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", "0", Tk_Offset(Table, padX), 0},
266    {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", "0", Tk_Offset(Table, padY), 0},
267    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", "sunken",
268     Tk_Offset(Table, defaultTag.relief), 0},
269    {TK_CONFIG_CUSTOM, "-resizeborders", "resizeBorders", "ResizeBorders",
270     "both", Tk_Offset(Table, resize), 0, &resizeTypeOpt },
271    {TK_CONFIG_INT, "-rowheight", "rowHeight", "RowHeight", "1",
272     Tk_Offset(Table, defRowHeight), 0},
273    {TK_CONFIG_INT, "-roworigin", "rowOrigin", "Origin", "0",
274     Tk_Offset(Table, rowOffset), 0},
275    {TK_CONFIG_INT, "-rows", "rows", "Rows", "10", Tk_Offset(Table, rows), 0},
276    {TK_CONFIG_STRING, "-rowseparator", "rowSeparator", "Separator", NULL,
277     Tk_Offset(Table, rowSep), TK_CONFIG_NULL_OK },
278    {TK_CONFIG_CUSTOM, "-rowstretchmode", "rowStretch", "StretchMode", "none",
279     Tk_Offset(Table, rowStretch), 0 , &stretchOpt },
280    {TK_CONFIG_STRING, "-rowtagcommand", "rowTagCommand", "TagCommand", NULL,
281     Tk_Offset(Table, rowTagCmd), TK_CONFIG_NULL_OK },
282    {TK_CONFIG_SYNONYM, "-selcmd", "selectionCommand", (char *)NULL,
283     (char *)NULL, 0, TK_CONFIG_NULL_OK},
284    {TK_CONFIG_STRING, "-selectioncommand", "selectionCommand",
285     "SelectionCommand", NULL, Tk_Offset(Table, selCmd), TK_CONFIG_NULL_OK },
286    {TK_CONFIG_STRING, "-selectmode", "selectMode", "SelectMode", "browse",
287     Tk_Offset(Table, selectMode), TK_CONFIG_NULL_OK },
288    {TK_CONFIG_BOOLEAN, "-selecttitles", "selectTitles", "SelectTitles", "0",
289     Tk_Offset(Table, selectTitles), 0},
290    {TK_CONFIG_CUSTOM, "-selecttype", "selectType", "SelectType", "cell",
291     Tk_Offset(Table, selectType), 0, &selTypeOpt },
292#ifdef PROCS
293    {TK_CONFIG_BOOLEAN, "-showprocs", "showProcs", "showProcs", "0",
294     Tk_Offset(Table, showProcs), 0},
295#endif
296    {TK_CONFIG_BOOLEAN, "-sparsearray", "sparseArray", "SparseArray", "1",
297     Tk_Offset(Table, sparse), 0},
298    {TK_CONFIG_CUSTOM, "-state", "state", "State", "normal",
299     Tk_Offset(Table, state), 0, &stateTypeOpt},
300    {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", (char *)NULL,
301     Tk_Offset(Table, takeFocus), TK_CONFIG_NULL_OK },
302    {TK_CONFIG_INT, "-titlecols", "titleCols", "TitleCols", "0",
303     Tk_Offset(Table, titleCols), TK_CONFIG_NULL_OK },
304#ifdef TITLE_CURSOR
305    {TK_CONFIG_CURSOR, "-titlecursor", "titleCursor", "Cursor", "arrow",
306     Tk_Offset(Table, titleCursor), TK_CONFIG_NULL_OK },
307#endif
308    {TK_CONFIG_INT, "-titlerows", "titleRows", "TitleRows", "0",
309     Tk_Offset(Table, titleRows), TK_CONFIG_NULL_OK },
310    {TK_CONFIG_BOOLEAN, "-usecommand", "useCommand", "UseCommand", "1",
311     Tk_Offset(Table, useCmd), 0},
312    {TK_CONFIG_STRING, "-variable", "variable", "Variable", (char *)NULL,
313     Tk_Offset(Table, arrayVar), TK_CONFIG_NULL_OK },
314    {TK_CONFIG_BOOLEAN, "-validate", "validate", "Validate", "0",
315     Tk_Offset(Table, validate), 0},
316    {TK_CONFIG_STRING, "-validatecommand", "validateCommand", "ValidateCommand",
317     "", Tk_Offset(Table, valCmd), TK_CONFIG_NULL_OK},
318    {TK_CONFIG_SYNONYM, "-vcmd", "validateCommand", (char *)NULL,
319     (char *)NULL, 0, TK_CONFIG_NULL_OK},
320    {TK_CONFIG_INT, "-width", "width", "Width", "0",
321     Tk_Offset(Table, maxReqCols), 0},
322    {TK_CONFIG_BOOLEAN, "-wrap", "wrap", "Wrap", "0",
323     Tk_Offset(Table, defaultTag.wrap), 0},
324    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
325     NULL, Tk_Offset(Table, xScrollCmd), TK_CONFIG_NULL_OK },
326    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
327     NULL, Tk_Offset(Table, yScrollCmd), TK_CONFIG_NULL_OK },
328    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL,
329     (char *)NULL, 0, 0}
330};
331
332/*
333 * This specifies the configure options that will cause an update to
334 * occur, so we should have a quick lookup table for them.
335 * Keep this in sync with the above values.
336 */
337
338static CONST84 char *updateOpts[] = {
339    "-anchor",		"-background",	"-bg",		"-bd",
340    "-borderwidth",	"-cache",	"-command",	"-colorigin",
341    "-cols",		"-colstretchmode",		"-coltagcommand",
342    "-drawmode",	"-fg",		"-font",	"-foreground",
343    "-hasprocs",	"-height",	"-highlightbackground",
344    "-highlightcolor",	"-highlightthickness",		"-insertbackground",
345    "-insertborderwidth",		"-insertwidth",	"-invertselected",
346    "-ipadx",		"-ipady",
347    "-maxheight",	"-maxwidth",	"-multiline",
348    "-padx",		"-pady",	"-relief",	"-roworigin",
349    "-rows",		"-rowstretchmode",		"-rowtagcommand",
350    "-showprocs",	"-state",	"-titlecols",	"-titlerows",
351    "-usecommand",	"-variable",	"-width",	"-wrap",
352    "-xscrollcommand",	"-yscrollcommand", (char *) NULL
353};
354
355#ifdef HAVE_TCL84
356/*
357 * The structure below defines widget class behavior by means of procedures
358 * that can be invoked from generic window code.
359 */
360
361static Tk_ClassProcs tableClass = {
362    sizeof(Tk_ClassProcs),	/* size */
363    TableWorldChanged,		/* worldChangedProc */
364    NULL,			/* createProc */
365    NULL			/* modalProc */
366};
367#endif
368
369#ifdef WIN32
370/*
371 * Some code from TkWinInt.h that we use to correct and speed up
372 * drawing of cells that need clipping in TableDisplay.
373 */
374typedef struct {
375    int type;
376    HWND handle;
377    void *winPtr;
378} TkWinWindow;
379
380typedef struct {
381    int type;
382    HBITMAP handle;
383    Colormap colormap;
384    int depth;
385} TkWinBitmap;
386
387typedef struct {
388    int type;
389    HDC hdc;
390} TkWinDC;
391
392typedef union {
393    int type;
394    TkWinWindow window;
395    TkWinBitmap bitmap;
396    TkWinDC winDC;
397} TkWinDrawable;
398#endif
399
400/*
401 * END HEADER INFORMATION
402 */
403
404/*
405 *---------------------------------------------------------------------------
406 *
407 * StringifyObjects -- (from tclCmdAH.c)
408 *
409 *	Helper function to bridge the gap between an object-based procedure
410 *	and an older string-based procedure.
411 *
412 *	Given an array of objects, allocate an array that consists of the
413 *	string representations of those objects.
414 *
415 * Results:
416 *	The return value is a pointer to the newly allocated array of
417 *	strings.  Elements 0 to (objc-1) of the string array point to the
418 *	string representation of the corresponding element in the source
419 *	object array; element objc of the string array is NULL.
420 *
421 * Side effects:
422 *	Memory allocated.  The caller must eventually free this memory
423 *	by calling ckfree() on the return value.
424 *
425	    int result;
426	    char **argv;
427	    argv   = StringifyObjects(objc, objv);
428	    result = StringBasedCmd(interp, objc, argv);
429	    ckfree((char *) argv);
430	    return result;
431 *
432 *---------------------------------------------------------------------------
433 */
434
435static char **
436StringifyObjects(objc, objv)
437     int objc;			/* Number of arguments. */
438     Tcl_Obj *CONST objv[];	/* Argument objects. */
439{
440    int i;
441    char **argv;
442
443    argv = (char **) ckalloc((objc + 1) * sizeof(char *));
444    for (i = 0; i < objc; i++) {
445    	argv[i] = Tcl_GetString(objv[i]);
446    }
447    argv[i] = NULL;
448    return argv;
449}
450
451/*
452 * As long as we wait for the Function in general
453 *
454 * This parses the "-class" option for the table.
455 */
456static int
457Tk_ClassOptionObjCmd(Tk_Window tkwin, char *defaultclass,
458		     int objc, Tcl_Obj *CONST objv[])
459{
460    char *classname = defaultclass;
461    int offset = 0;
462
463    if ((objc >= 4) && STREQ(Tcl_GetString(objv[2]),"-class")) {
464	classname = Tcl_GetString(objv[3]);
465	offset = 2;
466    }
467    Tk_SetClass(tkwin, classname);
468    return offset;
469}
470
471/*
472 *--------------------------------------------------------------
473 *
474 * Tk_TableObjCmd --
475 *	This procedure is invoked to process the "table" Tcl
476 *	command.  See the user documentation for details on what
477 *	it does.
478 *
479 * Results:
480 *	A standard Tcl result.
481 *
482 * Side effects:
483 *	See the user documentation.
484 *
485 *--------------------------------------------------------------
486 */
487static int
488Tk_TableObjCmd(clientData, interp, objc, objv)
489    ClientData clientData;	/* Main window associated with interpreter. */
490    Tcl_Interp *interp;
491    int objc;			/* Number of arguments. */
492    Tcl_Obj *CONST objv[];	/* Argument objects. */
493{
494    register Table *tablePtr;
495    Tk_Window tkwin, mainWin = (Tk_Window) clientData;
496    int offset;
497
498    if (objc < 2) {
499	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
500	return TCL_ERROR;
501    }
502
503    tkwin = Tk_CreateWindowFromPath(interp, mainWin, Tcl_GetString(objv[1]),
504	    (char *)NULL);
505    if (tkwin == NULL) {
506	return TCL_ERROR;
507    }
508
509    tablePtr			= (Table *) ckalloc(sizeof(Table));
510    memset((VOID *) tablePtr, 0, sizeof(Table));
511
512    /*
513     * Set the structure elments that aren't 0/NULL by default,
514     * and that won't be set by the initial configure call.
515     */
516    tablePtr->tkwin		= tkwin;
517    tablePtr->display		= Tk_Display(tkwin);
518    tablePtr->interp		= interp;
519    tablePtr->widgetCmd	= Tcl_CreateObjCommand(interp,
520	    Tk_PathName(tablePtr->tkwin), TableWidgetObjCmd,
521	    (ClientData) tablePtr, (Tcl_CmdDeleteProc *) TableCmdDeletedProc);
522
523    tablePtr->anchorRow		= -1;
524    tablePtr->anchorCol		= -1;
525    tablePtr->activeRow		= -1;
526    tablePtr->activeCol		= -1;
527    tablePtr->oldTopRow		= -1;
528    tablePtr->oldLeftCol	= -1;
529    tablePtr->oldActRow		= -1;
530    tablePtr->oldActCol		= -1;
531    tablePtr->seen[0]		= -1;
532
533    tablePtr->dataSource	= DATA_NONE;
534    tablePtr->activeBuf		= ckalloc(1);
535    *(tablePtr->activeBuf)	= '\0';
536
537    tablePtr->cursor		= None;
538    tablePtr->bdcursor		= None;
539
540    tablePtr->defaultTag.justify	= TK_JUSTIFY_LEFT;
541    tablePtr->defaultTag.state		= STATE_UNKNOWN;
542
543    /* misc tables */
544    tablePtr->tagTable	= (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
545    Tcl_InitHashTable(tablePtr->tagTable, TCL_STRING_KEYS);
546    tablePtr->winTable	= (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
547    Tcl_InitHashTable(tablePtr->winTable, TCL_STRING_KEYS);
548
549    /* internal value cache */
550    tablePtr->cache	= (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
551    Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS);
552
553    /* style hash tables */
554    tablePtr->colWidths = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
555    Tcl_InitHashTable(tablePtr->colWidths, TCL_ONE_WORD_KEYS);
556    tablePtr->rowHeights = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
557    Tcl_InitHashTable(tablePtr->rowHeights, TCL_ONE_WORD_KEYS);
558
559    /* style hash tables */
560    tablePtr->rowStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
561    Tcl_InitHashTable(tablePtr->rowStyles, TCL_ONE_WORD_KEYS);
562    tablePtr->colStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
563    Tcl_InitHashTable(tablePtr->colStyles, TCL_ONE_WORD_KEYS);
564    tablePtr->cellStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
565    Tcl_InitHashTable(tablePtr->cellStyles, TCL_STRING_KEYS);
566
567    /* special style hash tables */
568    tablePtr->flashCells = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
569    Tcl_InitHashTable(tablePtr->flashCells, TCL_STRING_KEYS);
570    tablePtr->selCells = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
571    Tcl_InitHashTable(tablePtr->selCells, TCL_STRING_KEYS);
572
573    /*
574     * List of tags in priority order.  30 is a good default number to alloc.
575     */
576    tablePtr->tagPrioMax = 30;
577    tablePtr->tagPrioNames = (char **) ckalloc(
578	sizeof(char *) * tablePtr->tagPrioMax);
579    tablePtr->tagPrios = (TableTag **) ckalloc(
580	sizeof(TableTag *) * tablePtr->tagPrioMax);
581    tablePtr->tagPrioSize = 0;
582    for (offset = 0; offset < tablePtr->tagPrioMax; offset++) {
583	tablePtr->tagPrioNames[offset] = (char *) NULL;
584	tablePtr->tagPrios[offset] = (TableTag *) NULL;
585    }
586
587#ifdef PROCS
588    tablePtr->inProc = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
589    Tcl_InitHashTable(tablePtr->inProc, TCL_STRING_KEYS);
590#endif
591
592    /*
593     * Handle class name and selection handlers
594     */
595    offset = 2 + Tk_ClassOptionObjCmd(tkwin, "Table", objc, objv);
596#ifdef HAVE_TCL84
597    Tk_SetClassProcs(tkwin, &tableClass, (ClientData) tablePtr);
598#endif
599    Tk_CreateEventHandler(tablePtr->tkwin,
600	    PointerMotionMask|ExposureMask|StructureNotifyMask|FocusChangeMask|VisibilityChangeMask,
601	    TableEventProc, (ClientData) tablePtr);
602    Tk_CreateSelHandler(tablePtr->tkwin, XA_PRIMARY, XA_STRING,
603	    TableFetchSelection, (ClientData) tablePtr, XA_STRING);
604
605    if (TableConfigure(interp, tablePtr, objc - offset, objv + offset,
606	    0, 1 /* force update */) != TCL_OK) {
607	Tk_DestroyWindow(tkwin);
608	return TCL_ERROR;
609    }
610    TableInitTags(tablePtr);
611
612    Tcl_SetObjResult(interp,
613	    Tcl_NewStringObj(Tk_PathName(tablePtr->tkwin), -1));
614    return TCL_OK;
615}
616
617/*
618 *--------------------------------------------------------------
619 *
620 * TableWidgetObjCmd --
621 *	This procedure is invoked to process the Tcl command
622 *	that corresponds to a widget managed by this module.
623 *	See the user documentation for details on what it does.
624 *
625 * Results:
626 *	A standard Tcl result.
627 *
628 * Side effects:
629 *	See the user documentation.
630 *
631 *--------------------------------------------------------------
632 */
633static int
634TableWidgetObjCmd(clientData, interp, objc, objv)
635     ClientData clientData;
636     Tcl_Interp *interp;
637     int objc;			/* Number of arguments. */
638     Tcl_Obj *CONST objv[];	/* Argument objects. */
639{
640    register Table *tablePtr = (Table *) clientData;
641    int row, col, i, cmdIndex, result = TCL_OK;
642
643    if (objc < 2) {
644	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
645	return TCL_ERROR;
646    }
647
648    /* parse the first parameter */
649    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
650				 "option", 0, &cmdIndex);
651    if (result != TCL_OK) {
652	return result;
653    }
654
655    Tcl_Preserve((ClientData) tablePtr);
656    switch ((enum command) cmdIndex) {
657	case CMD_ACTIVATE:
658	    result = Table_ActivateCmd(clientData, interp, objc, objv);
659	    break;
660
661	case CMD_BBOX:
662	    result = Table_BboxCmd(clientData, interp, objc, objv);
663	    break;
664
665	case CMD_BORDER:
666	    result = Table_BorderCmd(clientData, interp, objc, objv);
667	    break;
668
669	case CMD_CGET:
670	    if (objc != 3) {
671		Tcl_WrongNumArgs(interp, 2, objv, "option");
672		result = TCL_ERROR;
673	    } else {
674		result = Tk_ConfigureValue(interp, tablePtr->tkwin, tableSpecs,
675			(char *) tablePtr, Tcl_GetString(objv[2]), 0);
676	    }
677	    break;
678
679	case CMD_CLEAR:
680	    result = Table_ClearCmd(clientData, interp, objc, objv);
681	    break;
682
683	case CMD_CONFIGURE:
684	    if (objc < 4) {
685		result = Tk_ConfigureInfo(interp, tablePtr->tkwin, tableSpecs,
686			(char *) tablePtr, (objc == 3) ?
687			Tcl_GetString(objv[2]) : (char *) NULL, 0);
688	    } else {
689		result = TableConfigure(interp, tablePtr, objc - 2, objv + 2,
690			TK_CONFIG_ARGV_ONLY, 0);
691	    }
692	    break;
693
694	case CMD_CURSEL:
695	    result = Table_CurselectionCmd(clientData, interp, objc, objv);
696	    break;
697
698	case CMD_CURVALUE:
699	    result = Table_CurvalueCmd(clientData, interp, objc, objv);
700	    break;
701
702	case CMD_DELETE:
703	case CMD_INSERT:
704	    result = Table_EditCmd(clientData, interp, objc, objv);
705	    break;
706
707	case CMD_GET:
708	    result = Table_GetCmd(clientData, interp, objc, objv);
709	    break;
710
711	case CMD_HEIGHT:
712	case CMD_WIDTH:
713	    result = Table_AdjustCmd(clientData, interp, objc, objv);
714	    break;
715
716	case CMD_HIDDEN:
717	    result = Table_HiddenCmd(clientData, interp, objc, objv);
718	    break;
719
720	case CMD_ICURSOR:
721	    if (objc != 2 && objc != 3) {
722		Tcl_WrongNumArgs(interp, 2, objv, "?cursorPos?");
723		result = TCL_ERROR;
724		break;
725	    }
726	    if (!(tablePtr->flags & HAS_ACTIVE) ||
727		    (tablePtr->flags & ACTIVE_DISABLED) ||
728		    tablePtr->state == STATE_DISABLED) {
729		Tcl_SetObjResult(interp, Tcl_NewIntObj(-1));
730		break;
731	    } else if (objc == 3) {
732		if (TableGetIcursorObj(tablePtr, objv[2], NULL) != TCL_OK) {
733		    result = TCL_ERROR;
734		    break;
735		}
736		TableRefresh(tablePtr, tablePtr->activeRow,
737			tablePtr->activeCol, CELL);
738	    }
739	    Tcl_SetObjResult(interp, Tcl_NewIntObj(tablePtr->icursor));
740	    break;
741
742	case CMD_INDEX: {
743	    char *which = NULL;
744
745	    if (objc == 4) {
746		which = Tcl_GetString(objv[3]);
747	    }
748	    if ((objc < 3 || objc > 4) ||
749		    ((objc == 4) && (strcmp(which, "row")
750			    && strcmp(which, "col")))) {
751		Tcl_WrongNumArgs(interp, 2, objv, "<index> ?row|col?");
752		result = TCL_ERROR;
753	    } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col)
754		    != TCL_OK) {
755		result = TCL_ERROR;
756	    } else if (objc == 3) {
757		char buf[INDEX_BUFSIZE];
758		/* recreate the index, just in case it got bounded */
759		TableMakeArrayIndex(row, col, buf);
760		Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, -1));
761	    } else {	/* INDEX row|col */
762		Tcl_SetObjResult(interp,
763			Tcl_NewIntObj((*which == 'r') ? row : col));
764	    }
765	    break;
766	}
767
768#ifdef POSTSCRIPT
769	case CMD_POSTSCRIPT:
770	    result = Table_PostscriptCmd(clientData, interp, objc, objv);
771	    break;
772#endif
773
774	case CMD_REREAD:
775	    if (objc != 2) {
776		Tcl_WrongNumArgs(interp, 2, objv, NULL);
777		result = TCL_ERROR;
778	    } else if ((tablePtr->flags & HAS_ACTIVE) &&
779		    !(tablePtr->flags & ACTIVE_DISABLED) &&
780		    tablePtr->state != STATE_DISABLED) {
781		TableGetActiveBuf(tablePtr);
782		TableRefresh(tablePtr, tablePtr->activeRow,
783			tablePtr->activeCol, CELL|INV_FORCE);
784	    }
785	    break;
786
787	case CMD_SCAN:
788	    result = Table_ScanCmd(clientData, interp, objc, objv);
789	    break;
790
791	case CMD_SEE:
792	    if (objc != 3) {
793		Tcl_WrongNumArgs(interp, 2, objv, "index");
794		result = TCL_ERROR;
795	    } else if (TableGetIndexObj(tablePtr, objv[2],
796		    &row, &col) == TCL_ERROR) {
797		result = TCL_ERROR;
798	    } else {
799		/* Adjust from user to master coords */
800		row -= tablePtr->rowOffset;
801		col -= tablePtr->colOffset;
802		if (!TableCellVCoords(tablePtr, row, col, &i, &i, &i, &i, 1)) {
803		    tablePtr->topRow  = row-1;
804		    tablePtr->leftCol = col-1;
805		    TableAdjustParams(tablePtr);
806		}
807	    }
808	    break;
809
810	case CMD_SELECTION:
811	    if (objc < 3) {
812		Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
813		result = TCL_ERROR;
814		break;
815	    }
816	    if (Tcl_GetIndexFromObj(interp, objv[2], selCmdNames,
817		    "selection option", 0, &cmdIndex) != TCL_OK) {
818		result = TCL_ERROR;
819		break;
820	    }
821	    switch ((enum selCommand) cmdIndex) {
822		case CMD_SEL_ANCHOR:
823		    result = Table_SelAnchorCmd(clientData, interp,
824			    objc, objv);
825		    break;
826		case CMD_SEL_CLEAR:
827		    result = Table_SelClearCmd(clientData, interp, objc, objv);
828		    break;
829		case CMD_SEL_INCLUDES:
830		    result = Table_SelIncludesCmd(clientData, interp,
831			    objc, objv);
832		    break;
833		case CMD_SEL_PRESENT: {
834		    Tcl_HashSearch search;
835		    int present = (Tcl_FirstHashEntry(tablePtr->selCells,
836				    &search) != NULL);
837
838		    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(present));
839		    break;
840		}
841		case CMD_SEL_SET:
842		    result = Table_SelSetCmd(clientData, interp, objc, objv);
843		    break;
844	    }
845	    break;
846
847	case CMD_SET:
848	    result = Table_SetCmd(clientData, interp, objc, objv);
849	    break;
850
851	case CMD_SPANS:
852	    result = Table_SpanCmd(clientData, interp, objc, objv);
853	    break;
854
855	case CMD_TAG:
856	    result = Table_TagCmd(clientData, interp, objc, objv);
857	    break;
858
859	case CMD_VALIDATE:
860	    if (objc != 3) {
861		Tcl_WrongNumArgs(interp, 2, objv, "index");
862		result = TCL_ERROR;
863	    } else if (TableGetIndexObj(tablePtr, objv[2],
864		    &row, &col) == TCL_ERROR) {
865		result = TCL_ERROR;
866	    } else {
867		i = tablePtr->validate;
868		tablePtr->validate = 1;
869		result = TableValidateChange(tablePtr, row, col, (char *) NULL,
870			(char *) NULL, -1);
871		tablePtr->validate = i;
872		Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result == TCL_OK));
873		result = TCL_OK;
874	    }
875	    break;
876
877	case CMD_VERSION:
878	    if (objc != 2) {
879		Tcl_WrongNumArgs(interp, 2, objv, NULL);
880		result = TCL_ERROR;
881	    } else {
882		Tcl_SetObjResult(interp, Tcl_NewStringObj(PACKAGE_VERSION, -1));
883	    }
884	    break;
885
886	case CMD_WINDOW:
887	    result = Table_WindowCmd(clientData, interp, objc, objv);
888	    break;
889
890	case CMD_XVIEW:
891	case CMD_YVIEW:
892	    result = Table_ViewCmd(clientData, interp, objc, objv);
893	    break;
894    }
895
896    Tcl_Release((ClientData) tablePtr);
897    return result;
898}
899
900/*
901 *----------------------------------------------------------------------
902 *
903 * TableDestroy --
904 *	This procedure is invoked by Tcl_EventuallyFree
905 *	to clean up the internal structure of a table at a safe time
906 *	(when no-one is using it anymore).
907 *
908 * Results:
909 *	None.
910 *
911 * Side effects:
912 *	Everything associated with the table is freed up (hopefully).
913 *
914 *----------------------------------------------------------------------
915 */
916static void
917TableDestroy(ClientData clientdata)
918{
919    register Table *tablePtr = (Table *) clientdata;
920    Tcl_HashEntry *entryPtr;
921    Tcl_HashSearch search;
922
923    /* These may be repetitive from DestroyNotify, but it doesn't hurt */
924    /* cancel any pending update or timer */
925    if (tablePtr->flags & REDRAW_PENDING) {
926	Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr);
927	tablePtr->flags &= ~REDRAW_PENDING;
928    }
929    Tcl_DeleteTimerHandler(tablePtr->cursorTimer);
930    Tcl_DeleteTimerHandler(tablePtr->flashTimer);
931
932    /* delete the variable trace */
933    if (tablePtr->arrayVar != NULL) {
934	Tcl_UntraceVar(tablePtr->interp, tablePtr->arrayVar,
935		TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY,
936		(Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr);
937    }
938
939    /* free the int arrays */
940    if (tablePtr->colPixels) ckfree((char *) tablePtr->colPixels);
941    if (tablePtr->rowPixels) ckfree((char *) tablePtr->rowPixels);
942    if (tablePtr->colStarts) ckfree((char *) tablePtr->colStarts);
943    if (tablePtr->rowStarts) ckfree((char *) tablePtr->rowStarts);
944
945    /* delete cached active tag and string */
946    if (tablePtr->activeTagPtr) ckfree((char *) tablePtr->activeTagPtr);
947    if (tablePtr->activeBuf != NULL) ckfree(tablePtr->activeBuf);
948
949    /*
950     * Delete the various hash tables, make sure to clear the STRING_KEYS
951     * tables that allocate their strings:
952     *   cache, spanTbl (spanAffTbl shares spanTbl info)
953     */
954    Table_ClearHashTable(tablePtr->cache);
955    ckfree((char *) (tablePtr->cache));
956    Tcl_DeleteHashTable(tablePtr->rowStyles);
957    ckfree((char *) (tablePtr->rowStyles));
958    Tcl_DeleteHashTable(tablePtr->colStyles);
959    ckfree((char *) (tablePtr->colStyles));
960    Tcl_DeleteHashTable(tablePtr->cellStyles);
961    ckfree((char *) (tablePtr->cellStyles));
962    Tcl_DeleteHashTable(tablePtr->flashCells);
963    ckfree((char *) (tablePtr->flashCells));
964    Tcl_DeleteHashTable(tablePtr->selCells);
965    ckfree((char *) (tablePtr->selCells));
966    Tcl_DeleteHashTable(tablePtr->colWidths);
967    ckfree((char *) (tablePtr->colWidths));
968    Tcl_DeleteHashTable(tablePtr->rowHeights);
969    ckfree((char *) (tablePtr->rowHeights));
970#ifdef PROCS
971    Tcl_DeleteHashTable(tablePtr->inProc);
972    ckfree((char *) (tablePtr->inProc));
973#endif
974    if (tablePtr->spanTbl) {
975	Table_ClearHashTable(tablePtr->spanTbl);
976	ckfree((char *) (tablePtr->spanTbl));
977	Tcl_DeleteHashTable(tablePtr->spanAffTbl);
978	ckfree((char *) (tablePtr->spanAffTbl));
979    }
980
981    /* Now free up all the tag information */
982    for (entryPtr = Tcl_FirstHashEntry(tablePtr->tagTable, &search);
983	 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
984	TableCleanupTag(tablePtr, (TableTag *) Tcl_GetHashValue(entryPtr));
985	ckfree((char *) Tcl_GetHashValue(entryPtr));
986    }
987    /* free up the stuff in the default tag */
988    TableCleanupTag(tablePtr, &(tablePtr->defaultTag));
989    /* And delete the actual hash table */
990    Tcl_DeleteHashTable(tablePtr->tagTable);
991    ckfree((char *) (tablePtr->tagTable));
992    ckfree((char *) (tablePtr->tagPrios));
993    ckfree((char *) (tablePtr->tagPrioNames));
994
995    /* Now free up all the embedded window info */
996    for (entryPtr = Tcl_FirstHashEntry(tablePtr->winTable, &search);
997	 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
998	EmbWinDelete(tablePtr, (TableEmbWindow *) Tcl_GetHashValue(entryPtr));
999    }
1000    /* And delete the actual hash table */
1001    Tcl_DeleteHashTable(tablePtr->winTable);
1002    ckfree((char *) (tablePtr->winTable));
1003
1004    /* free the configuration options in the widget */
1005    Tk_FreeOptions(tableSpecs, (char *) tablePtr, tablePtr->display, 0);
1006
1007    /* and free the widget memory at last! */
1008    ckfree((char *) (tablePtr));
1009}
1010
1011/*
1012 *----------------------------------------------------------------------
1013 *
1014 * TableConfigure --
1015 *	This procedure is called to process an objc/objv list, plus
1016 *	the Tk option database, in order to configure (or reconfigure)
1017 *	a table widget.
1018 *
1019 * Results:
1020 *	The return value is a standard Tcl result.  If TCL_ERROR is
1021 *	returned, then interp result contains an error message.
1022 *
1023 * Side effects:
1024 *	Configuration information, such as colors, border width, etc.
1025 *	get set for tablePtr; old resources get freed, if there were any.
1026 *	Certain values might be constrained.
1027 *
1028 *----------------------------------------------------------------------
1029 */
1030static int
1031TableConfigure(interp, tablePtr, objc, objv, flags, forceUpdate)
1032     Tcl_Interp *interp;	/* Used for error reporting. */
1033     register Table *tablePtr;	/* Information about widget;  may or may
1034				 * not already have values for some fields. */
1035     int objc;			/* Number of arguments. */
1036     Tcl_Obj *CONST objv[];	/* Argument objects. */
1037     int flags;			/* Flags to pass to Tk_ConfigureWidget. */
1038     int forceUpdate;		/* Whether to force an update - required
1039				 * for initial configuration */
1040{
1041    Tcl_HashSearch search;
1042    int oldUse, oldCaching, oldExport, oldTitleRows, oldTitleCols;
1043    int result = TCL_OK;
1044    char *oldVar = NULL, **argv;
1045    Tcl_DString error;
1046    Tk_FontMetrics fm;
1047
1048    oldExport	= tablePtr->exportSelection;
1049    oldCaching	= tablePtr->caching;
1050    oldUse	= tablePtr->useCmd;
1051    oldTitleRows	= tablePtr->titleRows;
1052    oldTitleCols	= tablePtr->titleCols;
1053    if (tablePtr->arrayVar != NULL) {
1054	oldVar = ckalloc(strlen(tablePtr->arrayVar) + 1);
1055	strcpy(oldVar, tablePtr->arrayVar);
1056    }
1057
1058    /* Do the configuration */
1059    argv = StringifyObjects(objc, objv);
1060    result = Tk_ConfigureWidget(interp, tablePtr->tkwin, tableSpecs,
1061	    objc, (CONST84 char **) argv, (char *) tablePtr, flags);
1062    ckfree((char *) argv);
1063    if (result != TCL_OK) {
1064	return TCL_ERROR;
1065    }
1066
1067    Tcl_DStringInit(&error);
1068
1069    /* Any time we configure, reevaluate what our data source is */
1070    tablePtr->dataSource = DATA_NONE;
1071    if (tablePtr->caching) {
1072	tablePtr->dataSource |= DATA_CACHE;
1073    }
1074    if (tablePtr->command && tablePtr->useCmd) {
1075	tablePtr->dataSource |= DATA_COMMAND;
1076    } else if (tablePtr->arrayVar) {
1077	tablePtr->dataSource |= DATA_ARRAY;
1078    }
1079
1080    /* Check to see if the array variable was changed */
1081    if (strcmp((tablePtr->arrayVar ? tablePtr->arrayVar : ""),
1082	    (oldVar ? oldVar : ""))) {
1083	/* only do the following if arrayVar is our data source */
1084	if (tablePtr->dataSource & DATA_ARRAY) {
1085	    /*
1086	     * ensure that the cache will flush later
1087	     * so it gets the new values
1088	     */
1089	    oldCaching = !(tablePtr->caching);
1090	}
1091	/* remove the trace on the old array variable if there was one */
1092	if (oldVar != NULL)
1093	    Tcl_UntraceVar(interp, oldVar,
1094		    TCL_TRACE_WRITES|TCL_TRACE_UNSETS|TCL_GLOBAL_ONLY,
1095		    (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr);
1096	/* Check whether variable is an array and trace it if it is */
1097	if (tablePtr->arrayVar != NULL) {
1098	    /* does the variable exist as an array? */
1099	    if (Tcl_SetVar2(interp, tablePtr->arrayVar, TEST_KEY, "",
1100		    TCL_GLOBAL_ONLY) == NULL) {
1101		Tcl_DStringAppend(&error, "invalid variable value \"", -1);
1102		Tcl_DStringAppend(&error, tablePtr->arrayVar, -1);
1103		Tcl_DStringAppend(&error, "\": could not be made an array",
1104			-1);
1105		ckfree(tablePtr->arrayVar);
1106		tablePtr->arrayVar = NULL;
1107		tablePtr->dataSource &= ~DATA_ARRAY;
1108		result = TCL_ERROR;
1109	    } else {
1110		Tcl_UnsetVar2(interp, tablePtr->arrayVar, TEST_KEY,
1111			TCL_GLOBAL_ONLY);
1112		/* remove the effect of the evaluation */
1113		/* set a trace on the variable */
1114		Tcl_TraceVar(interp, tablePtr->arrayVar,
1115			TCL_TRACE_WRITES|TCL_TRACE_UNSETS|TCL_GLOBAL_ONLY,
1116			(Tcl_VarTraceProc *)TableVarProc,
1117			(ClientData) tablePtr);
1118
1119		/* only do the following if arrayVar is our data source */
1120		if (tablePtr->dataSource & DATA_ARRAY) {
1121		    /* get the current value of the selection */
1122		    TableGetActiveBuf(tablePtr);
1123		}
1124	    }
1125	}
1126    }
1127
1128    /* Free oldVar if it was allocated */
1129    if (oldVar != NULL) ckfree(oldVar);
1130
1131    if ((tablePtr->command && tablePtr->useCmd && !oldUse) ||
1132	(tablePtr->arrayVar && !(tablePtr->useCmd) && oldUse)) {
1133	/*
1134	 * Our effective data source changed, so flush and
1135	 * retrieve new active buffer
1136	 */
1137	Table_ClearHashTable(tablePtr->cache);
1138	Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS);
1139	TableGetActiveBuf(tablePtr);
1140	forceUpdate = 1;
1141    } else if (oldCaching != tablePtr->caching) {
1142	/*
1143	 * Caching changed, so just clear the cache for safety
1144	 */
1145	Table_ClearHashTable(tablePtr->cache);
1146	Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS);
1147	forceUpdate = 1;
1148    }
1149
1150    /*
1151     * Set up the default column width and row height
1152     */
1153    Tk_GetFontMetrics(tablePtr->defaultTag.tkfont, &fm);
1154    tablePtr->charWidth  = Tk_TextWidth(tablePtr->defaultTag.tkfont, "0", 1);
1155    tablePtr->charHeight = fm.linespace + 2;
1156
1157    if (tablePtr->insertWidth <= 0) {
1158	tablePtr->insertWidth = 2;
1159    }
1160    if (tablePtr->insertBorderWidth > tablePtr->insertWidth/2) {
1161	tablePtr->insertBorderWidth = tablePtr->insertWidth/2;
1162    }
1163    tablePtr->highlightWidth = MAX(0,tablePtr->highlightWidth);
1164
1165    /*
1166     * Ensure that certain values are within proper constraints
1167     */
1168    tablePtr->rows		= MAX(1, tablePtr->rows);
1169    tablePtr->cols		= MAX(1, tablePtr->cols);
1170    tablePtr->padX		= MAX(0, tablePtr->padX);
1171    tablePtr->padY		= MAX(0, tablePtr->padY);
1172    tablePtr->ipadX		= MAX(0, tablePtr->ipadX);
1173    tablePtr->ipadY		= MAX(0, tablePtr->ipadY);
1174    tablePtr->maxReqCols	= MAX(0, tablePtr->maxReqCols);
1175    tablePtr->maxReqRows	= MAX(0, tablePtr->maxReqRows);
1176    CONSTRAIN(tablePtr->titleRows, 0, tablePtr->rows);
1177    CONSTRAIN(tablePtr->titleCols, 0, tablePtr->cols);
1178
1179    /*
1180     * Handle change of default border style
1181     * The default borderwidth must be >= 0.
1182     */
1183    if (tablePtr->drawMode & (DRAW_MODE_SINGLE|DRAW_MODE_FAST)) {
1184	/*
1185	 * When drawing fast or single, the border must be <= 1.
1186	 * We have to do this after the normal configuration
1187	 * to base the borders off the first value given.
1188	 */
1189	tablePtr->defaultTag.bd[0]	= MIN(1, tablePtr->defaultTag.bd[0]);
1190	tablePtr->defaultTag.borders	= 1;
1191	ckfree((char *) tablePtr->defaultTag.borderStr);
1192	tablePtr->defaultTag.borderStr	= (char *) ckalloc(2);
1193	strcpy(tablePtr->defaultTag.borderStr,
1194		tablePtr->defaultTag.bd[0] ? "1" : "0");
1195    }
1196
1197    /*
1198     * Claim the selection if we've suddenly started exporting it and
1199     * there is a selection to export.
1200     */
1201    if (tablePtr->exportSelection && !oldExport &&
1202	(Tcl_FirstHashEntry(tablePtr->selCells, &search) != NULL)) {
1203	Tk_OwnSelection(tablePtr->tkwin, XA_PRIMARY, TableLostSelection,
1204		(ClientData) tablePtr);
1205    }
1206
1207    if ((tablePtr->titleRows < oldTitleRows) ||
1208	(tablePtr->titleCols < oldTitleCols)) {
1209	/*
1210	 * Prevent odd movement due to new possible topleft index
1211	 */
1212	if (tablePtr->titleRows < oldTitleRows)
1213	    tablePtr->topRow -= oldTitleRows - tablePtr->titleRows;
1214	if (tablePtr->titleCols < oldTitleCols)
1215	    tablePtr->leftCol -= oldTitleCols - tablePtr->titleCols;
1216	/*
1217	 * If our title area shrank, we need to check that the items
1218	 * within the new title area don't try to span outside it.
1219	 */
1220	TableSpanSanCheck(tablePtr);
1221    }
1222
1223    /*
1224     * Only do the full reconfigure if absolutely necessary
1225     */
1226    if (!forceUpdate) {
1227	int i, dummy;
1228	for (i = 0; i < objc-1; i += 2) {
1229	    if (Tcl_GetIndexFromObj(NULL, objv[i], updateOpts, "", 0, &dummy)
1230		    == TCL_OK) {
1231		forceUpdate = 1;
1232		break;
1233	    }
1234	}
1235    }
1236    if (forceUpdate) {
1237	/*
1238	 * Calculate the row and column starts
1239	 * Adjust the top left corner of the internal display
1240	 */
1241	TableAdjustParams(tablePtr);
1242	/* reset the cursor */
1243	TableConfigCursor(tablePtr);
1244	/* set up the background colour in the window */
1245	Tk_SetBackgroundFromBorder(tablePtr->tkwin, tablePtr->defaultTag.bg);
1246	/* set the geometry and border */
1247	TableGeometryRequest(tablePtr);
1248	Tk_SetInternalBorder(tablePtr->tkwin, tablePtr->highlightWidth);
1249	/* invalidate the whole table */
1250	TableInvalidateAll(tablePtr, INV_HIGHLIGHT);
1251    }
1252    /*
1253     * FIX this is goofy because the result could be munged by other
1254     * functions.  Could be improved.
1255     */
1256    Tcl_ResetResult(interp);
1257    if (result == TCL_ERROR) {
1258	Tcl_AddErrorInfo(interp, "\t(configuring table widget)");
1259	Tcl_DStringResult(interp, &error);
1260    }
1261    Tcl_DStringFree(&error);
1262    return result;
1263}
1264#ifdef HAVE_TCL84
1265/*
1266 *---------------------------------------------------------------------------
1267 *
1268 * TableWorldChanged --
1269 *
1270 *      This procedure is called when the world has changed in some
1271 *      way and the widget needs to recompute all its graphics contexts
1272 *	and determine its new geometry.
1273 *
1274 * Results:
1275 *      None.
1276 *
1277 * Side effects:
1278 *      Entry will be relayed out and redisplayed.
1279 *
1280 *---------------------------------------------------------------------------
1281 */
1282
1283static void
1284TableWorldChanged(instanceData)
1285    ClientData instanceData;	/* Information about widget. */
1286{
1287    Table *tablePtr = (Table *) instanceData;
1288    Tk_FontMetrics fm;
1289
1290    /*
1291     * Set up the default column width and row height
1292     */
1293    Tk_GetFontMetrics(tablePtr->defaultTag.tkfont, &fm);
1294    tablePtr->charWidth  = Tk_TextWidth(tablePtr->defaultTag.tkfont, "0", 1);
1295    tablePtr->charHeight = fm.linespace + 2;
1296
1297    /*
1298     * Recompute the window's geometry and arrange for it to be redisplayed.
1299     */
1300
1301    TableAdjustParams(tablePtr);
1302    TableGeometryRequest(tablePtr);
1303    Tk_SetInternalBorder(tablePtr->tkwin, tablePtr->highlightWidth);
1304    /* invalidate the whole table */
1305    TableInvalidateAll(tablePtr, INV_HIGHLIGHT);
1306}
1307#endif
1308/*
1309 *--------------------------------------------------------------
1310 *
1311 * TableEventProc --
1312 *	This procedure is invoked by the Tk dispatcher for various
1313 *	events on tables.
1314 *
1315 * Results:
1316 *	None.
1317 *
1318 * Side effects:
1319 *	When the window gets deleted, internal structures get
1320 *	cleaned up.  When it gets exposed, it is redisplayed.
1321 *
1322 *--------------------------------------------------------------
1323 */
1324static void
1325TableEventProc(clientData, eventPtr)
1326    ClientData clientData;	/* Information about window. */
1327    XEvent *eventPtr;		/* Information about event. */
1328{
1329    Table *tablePtr = (Table *) clientData;
1330    int row, col;
1331
1332    switch (eventPtr->type) {
1333	case MotionNotify:
1334	    if (!(tablePtr->resize & SEL_NONE)
1335		    && (tablePtr->bdcursor != None) &&
1336		    TableAtBorder(tablePtr, eventPtr->xmotion.x,
1337			    eventPtr->xmotion.y, &row, &col) &&
1338		    ((row>=0 && (tablePtr->resize & SEL_ROW)) ||
1339			    (col>=0 && (tablePtr->resize & SEL_COL)))) {
1340		/*
1341		 * The bordercursor is defined and we meet the criteria for
1342		 * being over a border.  Set the cursor to border if not
1343		 * already done.
1344		 */
1345		if (!(tablePtr->flags & OVER_BORDER)) {
1346		    tablePtr->flags |= OVER_BORDER;
1347		    Tk_DefineCursor(tablePtr->tkwin, tablePtr->bdcursor);
1348		}
1349	    } else if (tablePtr->flags & OVER_BORDER) {
1350		tablePtr->flags &= ~OVER_BORDER;
1351		if (tablePtr->cursor != None) {
1352		    Tk_DefineCursor(tablePtr->tkwin, tablePtr->cursor);
1353		} else {
1354		    Tk_UndefineCursor(tablePtr->tkwin);
1355		}
1356#ifdef TITLE_CURSOR
1357	    } else if (tablePtr->flags & (OVER_BORDER|OVER_TITLE)) {
1358		Tk_Cursor cursor = tablePtr->cursor;
1359
1360		//tablePtr->flags &= ~(OVER_BORDER|OVER_TITLE);
1361
1362		if (tablePtr->titleCursor != None) {
1363		    TableWhatCell(tablePtr, eventPtr->xmotion.x,
1364			    eventPtr->xmotion.y, &row, &col);
1365		    if ((row < tablePtr->titleRows) ||
1366			    (col < tablePtr->titleCols)) {
1367			if (tablePtr->flags & OVER_TITLE) {
1368			    break;
1369			}
1370			tablePtr->flags |= OVER_TITLE;
1371			cursor = tablePtr->titleCursor;
1372		    }
1373		}
1374		if (cursor != None) {
1375		    Tk_DefineCursor(tablePtr->tkwin, cursor);
1376		} else {
1377		    Tk_UndefineCursor(tablePtr->tkwin);
1378		}
1379	    } else if (tablePtr->titleCursor != None) {
1380		Tk_Cursor cursor = tablePtr->cursor;
1381
1382		TableWhatCell(tablePtr, eventPtr->xmotion.x,
1383			eventPtr->xmotion.y, &row, &col);
1384		if ((row < tablePtr->titleRows) ||
1385			(col < tablePtr->titleCols)) {
1386		    if (tablePtr->flags & OVER_TITLE) {
1387			break;
1388		    }
1389		    tablePtr->flags |= OVER_TITLE;
1390		    cursor = tablePtr->titleCursor;
1391		}
1392#endif
1393	    }
1394	    break;
1395
1396	case Expose:
1397	    TableInvalidate(tablePtr, eventPtr->xexpose.x, eventPtr->xexpose.y,
1398		    eventPtr->xexpose.width, eventPtr->xexpose.height,
1399		    INV_HIGHLIGHT);
1400	    break;
1401
1402	case DestroyNotify:
1403	    /* remove the command from the interpreter */
1404	    if (tablePtr->tkwin != NULL) {
1405		tablePtr->tkwin = NULL;
1406		Tcl_DeleteCommandFromToken(tablePtr->interp,
1407			tablePtr->widgetCmd);
1408	    }
1409
1410	    /* cancel any pending update or timer */
1411	    if (tablePtr->flags & REDRAW_PENDING) {
1412		Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr);
1413		tablePtr->flags &= ~REDRAW_PENDING;
1414	    }
1415	    Tcl_DeleteTimerHandler(tablePtr->cursorTimer);
1416	    Tcl_DeleteTimerHandler(tablePtr->flashTimer);
1417
1418	    Tcl_EventuallyFree((ClientData) tablePtr,
1419		    (Tcl_FreeProc *) TableDestroy);
1420	    break;
1421
1422	case MapNotify: /* redraw table when remapped if it changed */
1423	    if (tablePtr->flags & REDRAW_ON_MAP) {
1424		tablePtr->flags &= ~REDRAW_ON_MAP;
1425		Tcl_Preserve((ClientData) tablePtr);
1426		TableAdjustParams(tablePtr);
1427		TableInvalidateAll(tablePtr, INV_HIGHLIGHT);
1428		Tcl_Release((ClientData) tablePtr);
1429	    }
1430	    break;
1431
1432	case ConfigureNotify:
1433	    Tcl_Preserve((ClientData) tablePtr);
1434	    TableAdjustParams(tablePtr);
1435	    TableInvalidateAll(tablePtr, INV_HIGHLIGHT);
1436	    Tcl_Release((ClientData) tablePtr);
1437	    break;
1438
1439	case FocusIn:
1440	case FocusOut:
1441	    if (eventPtr->xfocus.detail != NotifyInferior) {
1442		tablePtr->flags |= REDRAW_BORDER;
1443		if (eventPtr->type == FocusOut) {
1444		    tablePtr->flags &= ~HAS_FOCUS;
1445		} else {
1446		    tablePtr->flags |= HAS_FOCUS;
1447		}
1448		TableRedrawHighlight(tablePtr);
1449		/* cancel the timer */
1450		TableConfigCursor(tablePtr);
1451	    }
1452	    break;
1453    }
1454}
1455
1456/*
1457 *----------------------------------------------------------------------
1458 *
1459 * TableCmdDeletedProc --
1460 *
1461 *	This procedure is invoked when a widget command is deleted.  If
1462 *	the widget isn't already in the process of being destroyed,
1463 *	this command destroys it.
1464 *
1465 * Results:
1466 *	None.
1467 *
1468 * Side effects:
1469 *	The widget is destroyed.
1470 *
1471 *----------------------------------------------------------------------
1472 */
1473static void
1474TableCmdDeletedProc(ClientData clientData)
1475{
1476    Table *tablePtr = (Table *) clientData;
1477    Tk_Window tkwin;
1478
1479    /*
1480     * This procedure could be invoked either because the window was
1481     * destroyed and the command was then deleted (in which case tkwin
1482     * is NULL) or because the command was deleted, and then this procedure
1483     * destroys the widget.
1484     */
1485
1486    if (tablePtr->tkwin != NULL) {
1487	tkwin = tablePtr->tkwin;
1488	tablePtr->tkwin = NULL;
1489	Tk_DestroyWindow(tkwin);
1490    }
1491}
1492
1493/*
1494 *----------------------------------------------------------------------
1495 *
1496 * TableRedrawHighlight --
1497 *	Redraws just the highlight for the window
1498 *
1499 * Results:
1500 *	None.
1501 *
1502 * Side effects:
1503 *	None
1504 *
1505 *----------------------------------------------------------------------
1506 */
1507static void
1508TableRedrawHighlight(Table *tablePtr)
1509{
1510    if ((tablePtr->flags & REDRAW_BORDER) && tablePtr->highlightWidth > 0) {
1511	GC gc = Tk_GCForColor((tablePtr->flags & HAS_FOCUS)
1512		? tablePtr->highlightColorPtr : tablePtr->highlightBgColorPtr,
1513		Tk_WindowId(tablePtr->tkwin));
1514	Tk_DrawFocusHighlight(tablePtr->tkwin, gc, tablePtr->highlightWidth,
1515		Tk_WindowId(tablePtr->tkwin));
1516    }
1517    tablePtr->flags &= ~REDRAW_BORDER;
1518}
1519
1520/*
1521 *----------------------------------------------------------------------
1522 *
1523 * TableRefresh --
1524 *	Refreshes an area of the table based on the mode.
1525 *	row,col in real coords (0-based)
1526 *
1527 * Results:
1528 *	Will cause redraw for visible cells
1529 *
1530 * Side effects:
1531 *	None.
1532 *
1533 *----------------------------------------------------------------------
1534 */
1535void
1536TableRefresh(register Table *tablePtr, int row, int col, int mode)
1537{
1538    int x, y, w, h;
1539
1540    if ((row < 0) || (col < 0)) {
1541	/*
1542	 * Invalid coords passed in.  This can happen when the "active" cell
1543	 * is refreshed, but doesn't really exist (row==-1 && col==-1).
1544	 */
1545	return;
1546    }
1547    if (mode & CELL) {
1548	if (TableCellVCoords(tablePtr, row, col, &x, &y, &w, &h, 0)) {
1549	    TableInvalidate(tablePtr, x, y, w, h, mode);
1550	}
1551    } else if (mode & ROW) {
1552	/* get the position of the leftmost cell in the row */
1553	if ((mode & INV_FILL) && row < tablePtr->topRow) {
1554	    /* Invalidate whole table */
1555	    TableInvalidateAll(tablePtr, mode);
1556	} else if (TableCellVCoords(tablePtr, row, tablePtr->leftCol,
1557		&x, &y, &w, &h, 0)) {
1558	    /* Invalidate from this row, maybe to end */
1559	    TableInvalidate(tablePtr, 0, y, Tk_Width(tablePtr->tkwin),
1560		    (mode&INV_FILL)?Tk_Height(tablePtr->tkwin):h, mode);
1561	}
1562    } else if (mode & COL) {
1563	/* get the position of the topmost cell on the column */
1564	if ((mode & INV_FILL) && col < tablePtr->leftCol) {
1565	    /* Invalidate whole table */
1566	    TableInvalidateAll(tablePtr, mode);
1567	} else if (TableCellVCoords(tablePtr, tablePtr->topRow, col,
1568		&x, &y, &w, &h, 0)) {
1569	    /* Invalidate from this column, maybe to end */
1570	    TableInvalidate(tablePtr, x, 0,
1571		    (mode&INV_FILL)?Tk_Width(tablePtr->tkwin):w,
1572		    Tk_Height(tablePtr->tkwin), mode);
1573	}
1574    }
1575}
1576
1577/*
1578 *----------------------------------------------------------------------
1579 *
1580 * TableGetGc --
1581 *	Gets a GC corresponding to the tag structure passed.
1582 *
1583 * Results:
1584 *	Returns usable GC.
1585 *
1586 * Side effects:
1587 *	None
1588 *
1589 *----------------------------------------------------------------------
1590 */
1591static void
1592TableGetGc(Display *display, Drawable d, TableTag *tagPtr, GC *tagGc)
1593{
1594    XGCValues gcValues;
1595    gcValues.foreground = Tk_3DBorderColor(tagPtr->fg)->pixel;
1596    gcValues.background = Tk_3DBorderColor(tagPtr->bg)->pixel;
1597    gcValues.font = Tk_FontId(tagPtr->tkfont);
1598    if (*tagGc == NULL) {
1599	gcValues.graphics_exposures = False;
1600	*tagGc = XCreateGC(display, d,
1601		GCForeground|GCBackground|GCFont|GCGraphicsExposures,
1602		&gcValues);
1603    } else {
1604	XChangeGC(display, *tagGc, GCForeground|GCBackground|GCFont,
1605		&gcValues);
1606    }
1607}
1608
1609#define TableFreeGc	XFreeGC
1610
1611/*
1612 *--------------------------------------------------------------
1613 *
1614 * TableUndisplay --
1615 *	This procedure removes the contents of a table window
1616 *	that have been moved offscreen.
1617 *
1618 * Results:
1619 *	Embedded windows can be unmapped.
1620 *
1621 * Side effects:
1622 *	Information disappears from the screen.
1623 *
1624 *--------------------------------------------------------------
1625 */
1626static void
1627TableUndisplay(register Table *tablePtr)
1628{
1629    register int *seen = tablePtr->seen;
1630    int row, col;
1631
1632    /* We need to find out the true last cell, not considering spans */
1633    tablePtr->flags |= AVOID_SPANS;
1634    TableGetLastCell(tablePtr, &row, &col);
1635    tablePtr->flags &= ~AVOID_SPANS;
1636
1637    if (seen[0] != -1) {
1638	if (seen[0] < tablePtr->topRow) {
1639	    /* Remove now hidden rows */
1640	    EmbWinUnmap(tablePtr, seen[0], MIN(seen[2],tablePtr->topRow-1),
1641		    seen[1], seen[3]);
1642	    /* Also account for the title area */
1643	    EmbWinUnmap(tablePtr, seen[0], MIN(seen[2],tablePtr->topRow-1),
1644		    0, tablePtr->titleCols-1);
1645	}
1646	if (seen[1] < tablePtr->leftCol) {
1647	    /* Remove now hidden cols */
1648	    EmbWinUnmap(tablePtr, seen[0], seen[2],
1649		    seen[1], MAX(seen[3],tablePtr->leftCol-1));
1650	    /* Also account for the title area */
1651	    EmbWinUnmap(tablePtr, 0, tablePtr->titleRows-1,
1652		    seen[1], MAX(seen[3],tablePtr->leftCol-1));
1653	}
1654	if (seen[2] > row) {
1655	    /* Remove now off-screen rows */
1656	    EmbWinUnmap(tablePtr, MAX(seen[0],row+1), seen[2],
1657		    seen[1], seen[3]);
1658	    /* Also account for the title area */
1659	    EmbWinUnmap(tablePtr, MAX(seen[0],row+1), seen[2],
1660		    0, tablePtr->titleCols-1);
1661	}
1662	if (seen[3] > col) {
1663	    /* Remove now off-screen cols */
1664	    EmbWinUnmap(tablePtr, seen[0], seen[2],
1665		    MAX(seen[1],col+1), seen[3]);
1666	    /* Also account for the title area */
1667	    EmbWinUnmap(tablePtr, 0, tablePtr->titleRows-1,
1668		    MAX(seen[1],col+1), seen[3]);
1669	}
1670    }
1671    seen[0] = tablePtr->topRow;
1672    seen[1] = tablePtr->leftCol;
1673    seen[2] = row;
1674    seen[3] = col;
1675}
1676
1677/*
1678 * Generally we should be able to use XSetClipRectangles on X11, but
1679 * the addition of Xft drawing to Tk 8.5+ completely ignores the clip
1680 * rectangles.  Thus turn it off for all cases until clip rectangles
1681 * are known to be respected. [Bug 1805350]
1682 */
1683#if 1 || defined(MAC_TCL) || defined(UNDER_CE) || (defined(WIN32) && defined(TCL_THREADS)) || defined(MAC_OSX_TK)
1684#define NO_XSETCLIP
1685#endif
1686/*
1687 *--------------------------------------------------------------
1688 *
1689 * TableDisplay --
1690 *	This procedure redraws the contents of a table window.
1691 *	The conditional code in this function is due to these factors:
1692 *		o Lack of XSetClipRectangles on Macintosh
1693 *		o Use of alternative routine for Windows
1694 *
1695 * Results:
1696 *	None.
1697 *
1698 * Side effects:
1699 *	Information appears on the screen.
1700 *
1701 *--------------------------------------------------------------
1702 */
1703static void
1704TableDisplay(ClientData clientdata)
1705{
1706    register Table *tablePtr = (Table *) clientdata;
1707    Tk_Window tkwin = tablePtr->tkwin;
1708    Display *display = tablePtr->display;
1709    Drawable window;
1710#ifdef NO_XSETCLIP
1711    Drawable clipWind;
1712#elif defined(WIN32)
1713    TkWinDrawable *twdPtr;
1714    HDC dc;
1715    HRGN clipR;
1716#else
1717    XRectangle clipRect;
1718#endif
1719    int rowFrom, rowTo, colFrom, colTo,
1720	invalidX, invalidY, invalidWidth, invalidHeight,
1721	x, y, width, height, itemX, itemY, itemW, itemH,
1722	row, col, urow, ucol, hrow=0, hcol=0, cx, cy, cw, ch, borders, bd[6],
1723	numBytes, new, boundW, boundH, maxW, maxH, cellType,
1724	originX, originY, activeCell, shouldInvert, ipadx, ipady, padx, pady;
1725    GC tagGc = NULL, topGc, bottomGc;
1726    char *string = NULL;
1727    char buf[INDEX_BUFSIZE];
1728    TableTag *tagPtr = NULL, *titlePtr, *selPtr, *activePtr, *flashPtr,
1729	*rowPtr, *colPtr;
1730    Tcl_HashEntry *entryPtr;
1731    static XPoint rect[3] = { {0, 0}, {0, 0}, {0, 0} };
1732    Tcl_HashTable *colTagsCache = NULL;
1733    Tcl_HashTable *drawnCache = NULL;
1734    Tk_TextLayout textLayout = NULL;
1735    TableEmbWindow *ewPtr;
1736    Tk_FontMetrics fm;
1737    Tk_Font ellFont = NULL;
1738    char *ellipsis = NULL;
1739    int ellLen = 0, useEllLen = 0, ellEast = 0;
1740
1741    tablePtr->flags &= ~REDRAW_PENDING;
1742    if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) {
1743	return;
1744    }
1745
1746    boundW = Tk_Width(tkwin) - tablePtr->highlightWidth;
1747    boundH = Tk_Height(tkwin) - tablePtr->highlightWidth;
1748
1749    /* Constrain drawable to not include highlight borders */
1750    invalidX = MAX(tablePtr->highlightWidth, tablePtr->invalidX);
1751    invalidY = MAX(tablePtr->highlightWidth, tablePtr->invalidY);
1752    invalidWidth  = MIN(tablePtr->invalidWidth, MAX(1, boundW-invalidX));
1753    invalidHeight = MIN(tablePtr->invalidHeight, MAX(1, boundH-invalidY));
1754
1755    ipadx = tablePtr->ipadX;
1756    ipady = tablePtr->ipadY;
1757    padx  = tablePtr->padX;
1758    pady  = tablePtr->padY;
1759
1760#ifndef WIN32
1761    /*
1762     * if we are using the slow drawing mode with a pixmap
1763     * create the pixmap and adjust x && y for offset in pixmap
1764     * FIX: Ignore slow mode for Win32 as the fast ClipRgn trick
1765     * below does not work for bitmaps.
1766     */
1767    if (tablePtr->drawMode == DRAW_MODE_SLOW) {
1768	window = Tk_GetPixmap(display, Tk_WindowId(tkwin),
1769		invalidWidth, invalidHeight, Tk_Depth(tkwin));
1770    } else
1771#endif
1772	window = Tk_WindowId(tkwin);
1773#ifdef NO_XSETCLIP
1774    clipWind = Tk_GetPixmap(display, window,
1775	    invalidWidth, invalidHeight, Tk_Depth(tkwin));
1776#endif
1777
1778    /* set up the permanent tag styles */
1779    entryPtr	= Tcl_FindHashEntry(tablePtr->tagTable, "title");
1780    titlePtr	= (TableTag *) Tcl_GetHashValue(entryPtr);
1781    entryPtr	= Tcl_FindHashEntry(tablePtr->tagTable, "sel");
1782    selPtr	= (TableTag *) Tcl_GetHashValue(entryPtr);
1783    entryPtr	= Tcl_FindHashEntry(tablePtr->tagTable, "active");
1784    activePtr	= (TableTag *) Tcl_GetHashValue(entryPtr);
1785    entryPtr	= Tcl_FindHashEntry(tablePtr->tagTable, "flash");
1786    flashPtr	= (TableTag *) Tcl_GetHashValue(entryPtr);
1787
1788    /* We need to find out the true cell span, not considering spans */
1789    tablePtr->flags |= AVOID_SPANS;
1790    /* find out the cells represented by the invalid region */
1791    TableWhatCell(tablePtr, invalidX, invalidY, &rowFrom, &colFrom);
1792    TableWhatCell(tablePtr, invalidX+invalidWidth-1,
1793	    invalidY+invalidHeight-1, &rowTo, &colTo);
1794    tablePtr->flags &= ~AVOID_SPANS;
1795
1796#ifdef DEBUG
1797    tcl_dprintf(tablePtr->interp, "%d,%d => %d,%d",
1798	    rowFrom+tablePtr->rowOffset, colFrom+tablePtr->colOffset,
1799	    rowTo+tablePtr->rowOffset, colTo+tablePtr->colOffset);
1800#endif
1801
1802    /*
1803     * Initialize colTagsCache hash table to cache column tag names.
1804     */
1805    colTagsCache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
1806    Tcl_InitHashTable(colTagsCache, TCL_ONE_WORD_KEYS);
1807    /*
1808     * Initialize drawnCache hash table to cache drawn cells.
1809     * This is necessary to prevent spanning cells being drawn multiple times.
1810     */
1811    drawnCache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
1812    Tcl_InitHashTable(drawnCache, TCL_STRING_KEYS);
1813
1814    /*
1815     * Create the tag here.  This will actually create a JoinTag
1816     * That will handle the priority management of merging for us.
1817     * We only need one allocated, and we'll reset it for each cell.
1818     */
1819    tagPtr = TableNewTag(tablePtr);
1820
1821    /* Cycle through the cells and display them */
1822    for (row = rowFrom; row <= rowTo; row++) {
1823	/*
1824	 * are we in the 'dead zone' between the
1825	 * title rows and the first displayed row
1826	 */
1827	if (row < tablePtr->topRow && row >= tablePtr->titleRows) {
1828	    row = tablePtr->topRow;
1829	}
1830
1831	/* Cache the row in user terms */
1832	urow = row+tablePtr->rowOffset;
1833
1834	/* Get the row tag once for all iterations of col */
1835	rowPtr = FindRowColTag(tablePtr, urow, ROW);
1836
1837	for (col = colFrom; col <= colTo; col++) {
1838	    activeCell = 0;
1839	    /*
1840	     * Adjust to first viewable column if we are in the 'dead zone'
1841	     * between the title cols and the first displayed column.
1842	     */
1843	    if (col < tablePtr->leftCol && col >= tablePtr->titleCols) {
1844		col = tablePtr->leftCol;
1845	    }
1846
1847	    /*
1848	     * Get the coordinates for the cell before possible rearrangement
1849	     * of row,col due to spanning cells
1850	     */
1851	    cellType = TableCellCoords(tablePtr, row, col,
1852		    &x, &y, &width, &height);
1853	    if (cellType == CELL_HIDDEN) {
1854		/*
1855		 * width,height holds the real start row,col of the span.
1856		 * Put the use cell ref into a buffer for the hash lookups.
1857		 */
1858		TableMakeArrayIndex(width, height, buf);
1859		Tcl_CreateHashEntry(drawnCache, buf, &new);
1860		if (!new) {
1861		    /* Not new in the entry, so it's already drawn */
1862		    continue;
1863		}
1864		hrow = row; hcol = col;
1865		row = width-tablePtr->rowOffset;
1866		col = height-tablePtr->colOffset;
1867		TableCellVCoords(tablePtr, row, col,
1868			&x, &y, &width, &height, 0);
1869		/* We have to adjust the coords back onto the visual display */
1870		urow = row+tablePtr->rowOffset;
1871		rowPtr = FindRowColTag(tablePtr, urow, ROW);
1872	    }
1873
1874	    /* Constrain drawn size to the visual boundaries */
1875	    if (width > boundW-x)	{ width  = boundW-x; }
1876	    if (height > boundH-y)	{ height = boundH-y; }
1877
1878	    /* Cache the col in user terms */
1879	    ucol = col+tablePtr->colOffset;
1880
1881	    /* put the use cell ref into a buffer for the hash lookups */
1882	    TableMakeArrayIndex(urow, ucol, buf);
1883	    if (cellType != CELL_HIDDEN) {
1884		Tcl_CreateHashEntry(drawnCache, buf, &new);
1885	    }
1886
1887	    /*
1888	     * Make sure we start with a clean tag (set to table defaults).
1889	     */
1890	    TableResetTag(tablePtr, tagPtr);
1891
1892	    /*
1893	     * Check to see if we have an embedded window in this cell.
1894	     */
1895	    entryPtr = Tcl_FindHashEntry(tablePtr->winTable, buf);
1896	    if (entryPtr != NULL) {
1897		ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);
1898
1899		if (ewPtr->tkwin != NULL) {
1900		    /* Display embedded window instead of text */
1901
1902		    /* if active, make it disabled to avoid
1903		     * unnecessary editing */
1904		    if ((tablePtr->flags & HAS_ACTIVE)
1905			    && row == tablePtr->activeRow
1906			    && col == tablePtr->activeCol) {
1907			tablePtr->flags |= ACTIVE_DISABLED;
1908		    }
1909
1910		    /*
1911		     * The EmbWinDisplay function may modify values in
1912		     * tagPtr, so reference those after this call.
1913		     */
1914		    EmbWinDisplay(tablePtr, window, ewPtr, tagPtr,
1915			    x, y, width, height);
1916
1917#ifndef WIN32
1918		    if (tablePtr->drawMode == DRAW_MODE_SLOW) {
1919			/* Correctly adjust x && y with the offset */
1920			x -= invalidX;
1921			y -= invalidY;
1922		    }
1923#endif
1924
1925		    Tk_Fill3DRectangle(tkwin, window, tagPtr->bg, x, y, width,
1926			    height, 0, TK_RELIEF_FLAT);
1927
1928		    /* border width for cell should now be properly set */
1929		    borders = TableGetTagBorders(tagPtr, &bd[0], &bd[1],
1930			    &bd[2], &bd[3]);
1931		    bd[4] = (bd[0] + bd[1])/2;
1932		    bd[5] = (bd[2] + bd[3])/2;
1933
1934		    goto DrawBorder;
1935		}
1936	    }
1937
1938	    /*
1939	     * Don't draw what won't be seen.
1940	     * Embedded windows handle this in EmbWinDisplay.
1941	     */
1942	    if ((width <= 0) || (height <= 0)) { continue; }
1943
1944#ifndef WIN32
1945	    if (tablePtr->drawMode == DRAW_MODE_SLOW) {
1946		/* Correctly adjust x && y with the offset */
1947		x -= invalidX;
1948		y -= invalidY;
1949	    }
1950#endif
1951
1952	    shouldInvert = 0;
1953	    /*
1954	     * Get the combined tag structure for the cell.
1955	     * First clear out a new tag structure that we will build in
1956	     * then add tags as we realize they belong.
1957	     *
1958	     * Tags have their own priorities which TableMergeTag will
1959	     * take into account when merging tags.
1960	     */
1961
1962	    /*
1963	     * Merge colPtr if it exists
1964	     * let's see if we have the value cached already
1965	     * if not, run the findColTag routine and cache the value
1966	     */
1967	    entryPtr = Tcl_CreateHashEntry(colTagsCache, (char *)ucol, &new);
1968	    if (new) {
1969		colPtr = FindRowColTag(tablePtr, ucol, COL);
1970		Tcl_SetHashValue(entryPtr, colPtr);
1971	    } else {
1972		colPtr = (TableTag *) Tcl_GetHashValue(entryPtr);
1973	    }
1974	    if (colPtr != (TableTag *) NULL) {
1975		TableMergeTag(tablePtr, tagPtr, colPtr);
1976	    }
1977	    /* Merge rowPtr if it exists */
1978	    if (rowPtr != (TableTag *) NULL) {
1979		TableMergeTag(tablePtr, tagPtr, rowPtr);
1980	    }
1981	    /* Am I in the titles */
1982	    if (row < tablePtr->titleRows || col < tablePtr->titleCols) {
1983		TableMergeTag(tablePtr, tagPtr, titlePtr);
1984	    }
1985	    /* Does this have a cell tag */
1986	    entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf);
1987	    if (entryPtr != NULL) {
1988		TableMergeTag(tablePtr, tagPtr,
1989			(TableTag *) Tcl_GetHashValue(entryPtr));
1990	    }
1991	    /* is this cell active? */
1992	    if ((tablePtr->flags & HAS_ACTIVE) &&
1993		    (tablePtr->state == STATE_NORMAL) &&
1994		    row == tablePtr->activeRow && col == tablePtr->activeCol) {
1995		if (tagPtr->state == STATE_DISABLED) {
1996		    tablePtr->flags |= ACTIVE_DISABLED;
1997		} else {
1998		    TableMergeTag(tablePtr, tagPtr, activePtr);
1999		    activeCell = 1;
2000		    tablePtr->flags &= ~ACTIVE_DISABLED;
2001		}
2002	    }
2003	    /* is this cell selected? */
2004	    if (Tcl_FindHashEntry(tablePtr->selCells, buf) != NULL) {
2005		if (tablePtr->invertSelected && !activeCell) {
2006		    shouldInvert = 1;
2007		} else {
2008		    TableMergeTag(tablePtr, tagPtr, selPtr);
2009		}
2010	    }
2011	    /* if flash mode is on, is this cell flashing? */
2012	    if (tablePtr->flashMode &&
2013		    Tcl_FindHashEntry(tablePtr->flashCells, buf) != NULL) {
2014		TableMergeTag(tablePtr, tagPtr, flashPtr);
2015	    }
2016
2017	    if (shouldInvert) {
2018		TableInvertTag(tagPtr);
2019	    }
2020
2021	    /*
2022	     * Borders for cell should now be properly set
2023	     */
2024	    borders = TableGetTagBorders(tagPtr, &bd[0], &bd[1],
2025		    &bd[2], &bd[3]);
2026	    bd[4] = (bd[0] + bd[1])/2;
2027	    bd[5] = (bd[2] + bd[3])/2;
2028
2029	    /*
2030	     * First fill in a blank rectangle.
2031	     */
2032	    Tk_Fill3DRectangle(tkwin, window, tagPtr->bg,
2033		    x, y, width, height, 0, TK_RELIEF_FLAT);
2034
2035	    /*
2036	     * Correct the dimensions to enforce padding constraints
2037	     */
2038	    width  -= bd[0] + bd[1] + (2 * padx);
2039	    height -= bd[2] + bd[3] + (2 * pady);
2040
2041	    /*
2042	     * Don't draw what won't be seen, based on border constraints.
2043	     */
2044	    if ((width <= 0) || (height <= 0)) {
2045		/*
2046		 * Re-Correct the dimensions before border drawing
2047		 */
2048		width  += bd[0] + bd[1] + (2 * padx);
2049		height += bd[2] + bd[3] + (2 * pady);
2050		goto DrawBorder;
2051	    }
2052
2053	    /*
2054	     * If an image is in the tag, draw it
2055	     */
2056	    if (tagPtr->image != NULL) {
2057		Tk_SizeOfImage(tagPtr->image, &itemW, &itemH);
2058		/* Handle anchoring of image in cell space */
2059		switch (tagPtr->anchor) {
2060		    case TK_ANCHOR_NW:
2061		    case TK_ANCHOR_W:
2062		    case TK_ANCHOR_SW:		/* western position */
2063			originX = itemX = 0;
2064			break;
2065		    case TK_ANCHOR_N:
2066		    case TK_ANCHOR_S:
2067		    case TK_ANCHOR_CENTER:	/* centered position */
2068			itemX	= MAX(0, (itemW - width) / 2);
2069			originX	= MAX(0, (width - itemW) / 2);
2070			break;
2071		    default:			/* eastern position */
2072			itemX	= MAX(0, itemW - width);
2073			originX	= MAX(0, width - itemW);
2074		}
2075		switch (tagPtr->anchor) {
2076		    case TK_ANCHOR_N:
2077		    case TK_ANCHOR_NE:
2078		    case TK_ANCHOR_NW:		/* northern position */
2079			originY = itemY = 0;
2080			break;
2081		    case TK_ANCHOR_W:
2082		    case TK_ANCHOR_E:
2083		    case TK_ANCHOR_CENTER:	/* centered position */
2084			itemY	= MAX(0, (itemH - height) / 2);
2085			originY	= MAX(0, (height - itemH) / 2);
2086			break;
2087		    default:			/* southern position */
2088			itemY	= MAX(0, itemH - height);
2089			originY	= MAX(0, height - itemH);
2090		}
2091		Tk_RedrawImage(tagPtr->image, itemX, itemY,
2092			MIN(itemW, width-originX), MIN(itemH, height-originY),
2093			window, x + originX + bd[0] + padx,
2094			y + originY + bd[2] + pady);
2095		/*
2096		 * If we don't want to display the text as well, then jump.
2097		 */
2098		if (tagPtr->showtext == 0) {
2099		    /*
2100		     * Re-Correct the dimensions before border drawing
2101		     */
2102		    width  += bd[0] + bd[1] + (2 * padx);
2103		    height += bd[2] + bd[3] + (2 * pady);
2104		    goto DrawBorder;
2105		}
2106	    }
2107
2108	    /*
2109	     * Get the GC for this particular blend of tags.
2110	     * This creates the GC if it never existed, otherwise it
2111	     * modifies the one we have, so we only need the one
2112	     */
2113	    TableGetGc(display, window, tagPtr, &tagGc);
2114
2115	    /* if this is the active cell, use the buffer */
2116	    if (activeCell) {
2117		string = tablePtr->activeBuf;
2118	    } else {
2119		/* Is there a value in the cell? If so, draw it  */
2120		string = TableGetCellValue(tablePtr, urow, ucol);
2121	    }
2122
2123#ifdef TCL_UTF_MAX
2124	    /*
2125	     * We have to use strlen here because otherwise it stops
2126	     * at the first \x00 unicode char it finds (!= '\0'),
2127	     * although there can be more to the string than that
2128	     */
2129	    numBytes = Tcl_NumUtfChars(string, (int) strlen(string));
2130#else
2131	    numBytes = strlen(string);
2132#endif
2133
2134	    /* If there is a string, show it */
2135	    if (activeCell || numBytes) {
2136		register int x0 = x + bd[0] + padx;
2137		register int y0 = y + bd[2] + pady;
2138
2139		/* get the dimensions of the string */
2140		textLayout = Tk_ComputeTextLayout(tagPtr->tkfont,
2141			string, numBytes,
2142			(tagPtr->wrap > 0) ? width : 0, tagPtr->justify,
2143			(tagPtr->multiline > 0) ? 0 : TK_IGNORE_NEWLINES,
2144			&itemW, &itemH);
2145
2146		/*
2147		 * Set the origin coordinates of the string to draw using
2148		 * the anchor.  origin represents the (x,y) coordinate of
2149		 * the lower left corner of the text box, relative to the
2150		 * internal (inside the border) window
2151		 */
2152
2153		/* set the X origin first */
2154		switch (tagPtr->anchor) {
2155		    case TK_ANCHOR_NW:
2156		    case TK_ANCHOR_W:
2157		    case TK_ANCHOR_SW:		/* western position */
2158			originX = ipadx;
2159			break;
2160		    case TK_ANCHOR_N:
2161		    case TK_ANCHOR_S:
2162		    case TK_ANCHOR_CENTER:	/* centered position */
2163			originX = (width - itemW) / 2;
2164			break;
2165		    default:			/* eastern position */
2166			originX = width - itemW - ipadx;
2167		}
2168
2169		/* then set the Y origin */
2170		switch (tagPtr->anchor) {
2171		    case TK_ANCHOR_N:
2172		    case TK_ANCHOR_NE:
2173		    case TK_ANCHOR_NW:		/* northern position */
2174			originY = ipady;
2175			break;
2176		    case TK_ANCHOR_W:
2177		    case TK_ANCHOR_E:
2178		    case TK_ANCHOR_CENTER:	/* centered position */
2179			originY = (height - itemH) / 2;
2180			break;
2181		    default:			/* southern position */
2182			originY = height - itemH - ipady;
2183		}
2184
2185		/*
2186		 * If this is the active cell and we are editing,
2187		 * ensure that the cursor will be displayed
2188		 */
2189		if (activeCell) {
2190		    Tk_CharBbox(textLayout, tablePtr->icursor,
2191			    &cx, &cy, &cw, &ch);
2192		    /* we have to fudge with maxW because of odd width
2193		     * determination for newlines at the end of a line */
2194		    maxW = width - tablePtr->insertWidth
2195			- (cx + MIN(tablePtr->charWidth, cw));
2196		    maxH = height - (cy + ch);
2197		    if (originX < bd[0] - cx) {
2198			/* cursor off cell to the left */
2199			/* use western positioning to cet cursor at left
2200			 * with slight variation to show some text */
2201			originX = bd[0] - cx
2202			    + MIN(cx, width - tablePtr->insertWidth);
2203		    } else if (originX > maxW) {
2204			/* cursor off cell to the right */
2205			/* use eastern positioning to cet cursor at right */
2206			originX = maxW;
2207		    }
2208		    if (originY < bd[2] - cy) {
2209			/* cursor before top of cell */
2210			/* use northern positioning to cet cursor at top */
2211			originY = bd[2] - cy;
2212		    } else if (originY > maxH) {
2213			/* cursor beyond bottom of cell */
2214			/* use southern positioning to cet cursor at bottom */
2215			originY = maxH;
2216		    }
2217		    tablePtr->activeTagPtr	= tagPtr;
2218		    tablePtr->activeX		= originX;
2219		    tablePtr->activeY		= originY;
2220		}
2221
2222		/*
2223		 * Use a clip rectangle only if necessary as it means
2224		 * updating the GC in the server which slows everything down.
2225		 * We can't fudge the width or height, just in case the user
2226		 * wanted empty pad space.
2227		 */
2228		if ((originX < 0) || (originY < 0) ||
2229			(originX+itemW > width) || (originY+itemH > height)) {
2230		    if (!activeCell
2231			    && (tagPtr->ellipsis != NULL)
2232			    && (tagPtr->wrap <= 0)
2233			    && (tagPtr->multiline <= 0)
2234			) {
2235			/*
2236			 * Check which side to draw ellipsis on
2237			 */
2238			switch (tagPtr->anchor) {
2239			    case TK_ANCHOR_NE:
2240			    case TK_ANCHOR_E:
2241			    case TK_ANCHOR_SE:		/* eastern position */
2242				ellEast = 0;
2243				break;
2244			    default:			/* western position */
2245				ellEast = 1;
2246			}
2247			if ((ellipsis != tagPtr->ellipsis)
2248				|| (ellFont != tagPtr->tkfont)) {
2249			    /*
2250			     * Different ellipsis from last cached
2251			     */
2252			    ellFont  = tagPtr->tkfont;
2253			    ellipsis = tagPtr->ellipsis;
2254			    ellLen = Tk_TextWidth(ellFont,
2255				    ellipsis, (int) strlen(ellipsis));
2256			    Tk_GetFontMetrics(tagPtr->tkfont, &fm);
2257			}
2258			useEllLen = MIN(ellLen, width);
2259		    } else {
2260			ellEast = 0;
2261			useEllLen = 0;
2262		    }
2263
2264		    /*
2265		     * The text wants to overflow the boundaries of the
2266		     * displayed cell, so we must clip in some way
2267		     */
2268#ifdef NO_XSETCLIP
2269		    /*
2270		     * This code is basically for the Macintosh.
2271		     * Copy the the current contents of the cell into the
2272		     * clipped window area.  This keeps any fg/bg and image
2273		     * data intact.
2274		     * x0 - x == pad area
2275		     */
2276		    XCopyArea(display, window, clipWind, tagGc, x0, y0,
2277			    width, height, x0 - x, y0 - y);
2278		    /*
2279		     * Now draw into the cell space on the special window.
2280		     * Don't use x,y base offset for clipWind.
2281		     */
2282		    Tk_DrawTextLayout(display, clipWind, tagGc, textLayout,
2283			    x0 - x + originX, y0 - y + originY, 0, -1);
2284
2285		    if (useEllLen) {
2286			/*
2287			 * Recopy area the ellipse covers (not efficient)
2288			 */
2289			XCopyArea(display, window, clipWind, tagGc,
2290				x0 + (ellEast ? width - useEllLen : 0), y0,
2291				useEllLen, height,
2292				x0 - x + (ellEast ? width - useEllLen : 0),
2293				y0 - y);
2294			Tk_DrawChars(display, clipWind, tagGc, ellFont,
2295				ellipsis, (int) strlen(ellipsis),
2296				x0 - x + (ellEast ? width - useEllLen : 0),
2297				y0 - y + originY + fm.ascent);
2298		    }
2299		    /*
2300		     * Now copy back only the area that we want the
2301		     * text to be drawn on.
2302		     */
2303		    XCopyArea(display, clipWind, window, tagGc,
2304			    x0 - x, y0 - y, width, height, x0, y0);
2305#elif defined(WIN32)
2306		    /*
2307		     * This is evil, evil evil! but the XCopyArea
2308		     * doesn't work in all cases - Michael Teske.
2309		     * The general structure follows the comments below.
2310		     */
2311		    twdPtr = (TkWinDrawable *) window;
2312		    dc     = GetDC(twdPtr->window.handle);
2313
2314		    clipR = CreateRectRgn(x0 + (ellEast ? 0 : useEllLen), y0,
2315			x0 + width - (ellEast ? useEllLen : 0), y0 + height);
2316
2317		    SelectClipRgn(dc, clipR);
2318		    DeleteObject(clipR);
2319		    /* OffsetClipRgn(dc, 0, 0); */
2320
2321		    Tk_DrawTextLayout(display, window, tagGc, textLayout,
2322			    x0 + originX, y0 + originY, 0, -1);
2323
2324		    if (useEllLen) {
2325			clipR = CreateRectRgn(x0, y0, x0 + width, y0 + height);
2326			SelectClipRgn(dc, clipR);
2327			DeleteObject(clipR);
2328			Tk_DrawChars(display, window, tagGc, ellFont,
2329				ellipsis, (int) strlen(ellipsis),
2330				x0 + (ellEast? width-useEllLen : 0),
2331				y0 + originY + fm.ascent);
2332		    }
2333		    SelectClipRgn(dc, NULL);
2334		    ReleaseDC(twdPtr->window.handle, dc);
2335#else
2336		    /*
2337		     * Use an X clipping rectangle.  The clipping is the
2338		     * rectangle just for the actual text space (to allow
2339		     * for empty padding space).
2340		     */
2341		    clipRect.x      = x0 + (ellEast ? 0 : useEllLen);
2342		    clipRect.y      = y0;
2343		    clipRect.width  = width - (ellEast ? useEllLen : 0);
2344		    clipRect.height = height;
2345		    XSetClipRectangles(display, tagGc, 0, 0, &clipRect, 1,
2346			    Unsorted);
2347		    Tk_DrawTextLayout(display, window, tagGc, textLayout,
2348			    x0 + originX,
2349			    y0 + originY, 0, -1);
2350		    if (useEllLen) {
2351			clipRect.x     = x0;
2352			clipRect.width = width;
2353			XSetClipRectangles(display, tagGc, 0, 0, &clipRect, 1,
2354				Unsorted);
2355			Tk_DrawChars(display, window, tagGc, ellFont,
2356				ellipsis, (int) strlen(ellipsis),
2357				x0 + (ellEast? width-useEllLen : 0),
2358				y0 + originY + fm.ascent);
2359		    }
2360		    XSetClipMask(display, tagGc, None);
2361#endif
2362		} else {
2363		    Tk_DrawTextLayout(display, window, tagGc, textLayout,
2364			    x0 + originX, y0 + originY, 0, -1);
2365		}
2366
2367		/* if this is the active cell draw the cursor if it's on.
2368		 * this ignores clip rectangles. */
2369		if (activeCell && (tablePtr->flags & CURSOR_ON) &&
2370			(originY + cy + bd[2] + pady < height) &&
2371			(originX + cx + bd[0] + padx -
2372				(tablePtr->insertWidth / 2) >= 0)) {
2373		    /* make sure it will fit in the box */
2374		    maxW = MAX(0, originY + cy + bd[2] + pady);
2375		    maxH = MIN(ch, height - maxW + bd[2] + pady);
2376		    Tk_Fill3DRectangle(tkwin, window, tablePtr->insertBg,
2377			    x0 + originX + cx - (tablePtr->insertWidth/2),
2378			    y + maxW, tablePtr->insertWidth,
2379			    maxH, 0, TK_RELIEF_FLAT);
2380		}
2381	    }
2382
2383	    /*
2384	     * Re-Correct the dimensions before border drawing
2385	     */
2386	    width  += bd[0] + bd[1] + (2 * padx);
2387	    height += bd[2] + bd[3] + (2 * pady);
2388
2389	    DrawBorder:
2390	    /* Draw the 3d border on the pixmap correctly offset */
2391	    if (tablePtr->drawMode == DRAW_MODE_SINGLE) {
2392		topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_DARK_GC);
2393		/* draw a line with single pixel width */
2394		rect[0].x = x;
2395		rect[0].y = y + height - 1;
2396		rect[1].y = -height + 1;
2397		rect[2].x = width - 1;
2398		XDrawLines(display, window, topGc, rect, 3, CoordModePrevious);
2399	    } else if (tablePtr->drawMode == DRAW_MODE_FAST) {
2400		/*
2401		 * This depicts a full 1 pixel border.
2402		 *
2403		 * Choose the GCs to get the best approximation
2404		 * to the desired drawing style.
2405		 */
2406		switch(tagPtr->relief) {
2407		    case TK_RELIEF_FLAT:
2408			topGc = bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg,
2409				TK_3D_FLAT_GC);
2410			break;
2411		    case TK_RELIEF_RAISED:
2412		    case TK_RELIEF_RIDGE:
2413			topGc    = Tk_3DBorderGC(tkwin, tagPtr->bg,
2414				TK_3D_LIGHT_GC);
2415			bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg,
2416				TK_3D_DARK_GC);
2417			break;
2418		    default: /* TK_RELIEF_SUNKEN TK_RELIEF_GROOVE */
2419			bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg,
2420				TK_3D_LIGHT_GC);
2421			topGc    = Tk_3DBorderGC(tkwin, tagPtr->bg,
2422				TK_3D_DARK_GC);
2423			break;
2424		}
2425
2426		/* draw a line with single pixel width */
2427		rect[0].x = x + width - 1;
2428		rect[0].y = y;
2429		rect[1].y = height - 1;
2430		rect[2].x = -width + 1;
2431		XDrawLines(display, window, bottomGc, rect, 3,
2432			CoordModePrevious);
2433		rect[0].x = x;
2434		rect[0].y = y + height - 1;
2435		rect[1].y = -height + 1;
2436		rect[2].x = width - 1;
2437		XDrawLines(display, window, topGc, rect, 3,
2438			CoordModePrevious);
2439	    } else {
2440		if (borders > 1) {
2441		    if (bd[0]) {
2442			Tk_3DVerticalBevel(tkwin, window, tagPtr->bg,
2443				x, y, bd[0], height,
2444				1 /* left side */, tagPtr->relief);
2445		    }
2446		    if (bd[1]) {
2447			Tk_3DVerticalBevel(tkwin, window, tagPtr->bg,
2448				x + width - bd[1], y, bd[1], height,
2449				0 /* right side */, tagPtr->relief);
2450		    }
2451		    if ((borders == 4) && bd[2]) {
2452			Tk_3DHorizontalBevel(tkwin, window, tagPtr->bg,
2453				x, y, width, bd[2],
2454				1, 1, 1 /* top */, tagPtr->relief);
2455		    }
2456		    if ((borders == 4) && bd[3]) {
2457			Tk_3DHorizontalBevel(tkwin, window, tagPtr->bg,
2458				x, y + height - bd[3], width, bd[3],
2459				0, 0, 0 /* bottom */, tagPtr->relief);
2460		    }
2461		} else if (borders == 1) {
2462		    Tk_Draw3DRectangle(tkwin, window, tagPtr->bg, x, y,
2463			    width, height, bd[0], tagPtr->relief);
2464		}
2465	    }
2466
2467	    /* clean up the necessaries */
2468	    if (tagPtr == tablePtr->activeTagPtr) {
2469		/*
2470		 * This means it was the activeCell with text displayed.
2471		 * We buffer the active tag for the 'activate' command.
2472		 */
2473		tablePtr->activeTagPtr = TableNewTag(NULL);
2474		memcpy((VOID *) tablePtr->activeTagPtr,
2475			(VOID *) tagPtr, sizeof(TableTag));
2476	    }
2477	    if (textLayout) {
2478		Tk_FreeTextLayout(textLayout);
2479		textLayout = NULL;
2480	    }
2481	    if (cellType == CELL_HIDDEN) {
2482		/* the last cell was a hidden one,
2483		 * rework row stuff back to normal */
2484		row = hrow; col = hcol;
2485		urow = row+tablePtr->rowOffset;
2486		rowPtr = FindRowColTag(tablePtr, urow, ROW);
2487	    }
2488	}
2489    }
2490    ckfree((char *) tagPtr);
2491#ifdef NO_XSETCLIP
2492    Tk_FreePixmap(display, clipWind);
2493#endif
2494
2495    /* Take care of removing embedded windows that are no longer in view */
2496    TableUndisplay(tablePtr);
2497
2498#ifndef WIN32
2499    /* copy over and delete the pixmap if we are in slow mode */
2500    if (tablePtr->drawMode == DRAW_MODE_SLOW) {
2501	/* Get a default valued GC */
2502	TableGetGc(display, window, &(tablePtr->defaultTag), &tagGc);
2503	XCopyArea(display, window, Tk_WindowId(tkwin), tagGc, 0, 0,
2504		(unsigned) invalidWidth, (unsigned) invalidHeight,
2505		invalidX, invalidY);
2506	Tk_FreePixmap(display, window);
2507	window = Tk_WindowId(tkwin);
2508    }
2509#endif
2510
2511    /*
2512     * If we are at the end of the table, clear the area after the last
2513     * row/col.  We discount spans here because we just need the coords
2514     * for the area that would be the last physical cell.
2515     */
2516    tablePtr->flags |= AVOID_SPANS;
2517    TableCellCoords(tablePtr, tablePtr->rows-1, tablePtr->cols-1,
2518	    &x, &y, &width, &height);
2519    tablePtr->flags &= ~AVOID_SPANS;
2520
2521    /* This should occur before moving pixmap, but this simplifies things
2522     *
2523     * Could use Tk_Fill3DRectangle instead of XFillRectangle
2524     * for best compatibility, and XClearArea could be used on Unix
2525     * for best speed, so this is the compromise w/o #ifdef's
2526     */
2527    if (x+width < invalidX+invalidWidth) {
2528	XFillRectangle(display, window,
2529		Tk_3DBorderGC(tkwin, tablePtr->defaultTag.bg, TK_3D_FLAT_GC),
2530		x+width, invalidY, (unsigned) invalidX+invalidWidth-x-width,
2531		(unsigned) invalidHeight);
2532    }
2533
2534    if (y+height < invalidY+invalidHeight) {
2535	XFillRectangle(display, window,
2536		Tk_3DBorderGC(tkwin, tablePtr->defaultTag.bg, TK_3D_FLAT_GC),
2537		invalidX, y+height, (unsigned) invalidWidth,
2538		(unsigned) invalidY+invalidHeight-y-height);
2539    }
2540
2541    if (tagGc != NULL) {
2542	TableFreeGc(display, tagGc);
2543    }
2544    TableRedrawHighlight(tablePtr);
2545    /*
2546     * Free the hash table used to cache evaluations.
2547     */
2548    Tcl_DeleteHashTable(colTagsCache);
2549    ckfree((char *) (colTagsCache));
2550    Tcl_DeleteHashTable(drawnCache);
2551    ckfree((char *) (drawnCache));
2552}
2553
2554/*
2555 *----------------------------------------------------------------------
2556 *
2557 * TableInvalidate --
2558 *	Invalidates a rectangle and adds it to the total invalid rectangle
2559 *	waiting to be redrawn.  If the INV_FORCE flag bit is set,
2560 *	it does an update instantly else waits until Tk is idle.
2561 *
2562 * Results:
2563 *	Will schedule table (re)display.
2564 *
2565 * Side effects:
2566 *	None
2567 *
2568 *----------------------------------------------------------------------
2569 */
2570void
2571TableInvalidate(Table * tablePtr, int x, int y,
2572		int w, int h, int flags)
2573{
2574    Tk_Window tkwin = tablePtr->tkwin;
2575    int hl	= tablePtr->highlightWidth;
2576    int height	= Tk_Height(tkwin);
2577    int width	= Tk_Width(tkwin);
2578
2579    /*
2580     * Make sure that the window hasn't been destroyed already.
2581     * Avoid allocating 0 sized pixmaps which would be fatal,
2582     * and check if rectangle is even on the screen.
2583     */
2584    if ((tkwin == NULL)
2585	    || (w <= 0) || (h <= 0) || (x > width) || (y > height)) {
2586	return;
2587    }
2588
2589    /* If not even mapped, wait for the remap to redraw all */
2590    if (!Tk_IsMapped(tkwin)) {
2591	tablePtr->flags |= REDRAW_ON_MAP;
2592	return;
2593    }
2594
2595    /*
2596     * If no pending updates exist, then replace the rectangle.
2597     * Otherwise find the bounding rectangle.
2598     */
2599    if ((flags & INV_HIGHLIGHT) &&
2600	    (x < hl || y < hl || x+w >= width-hl || y+h >= height-hl)) {
2601	tablePtr->flags |= REDRAW_BORDER;
2602    }
2603
2604    if (tablePtr->flags & REDRAW_PENDING) {
2605	tablePtr->invalidWidth = MAX(x + w,
2606		tablePtr->invalidX+tablePtr->invalidWidth);
2607	tablePtr->invalidHeight = MAX(y + h,
2608		tablePtr->invalidY+tablePtr->invalidHeight);
2609	if (tablePtr->invalidX > x) tablePtr->invalidX = x;
2610	if (tablePtr->invalidY > y) tablePtr->invalidY = y;
2611	tablePtr->invalidWidth  -= tablePtr->invalidX;
2612	tablePtr->invalidHeight -= tablePtr->invalidY;
2613	/* Do we want to force this update out? */
2614	if (flags & INV_FORCE) {
2615	    Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr);
2616	    TableDisplay((ClientData) tablePtr);
2617	}
2618    } else {
2619	tablePtr->invalidX = x;
2620	tablePtr->invalidY = y;
2621	tablePtr->invalidWidth = w;
2622	tablePtr->invalidHeight = h;
2623	if (flags & INV_FORCE) {
2624	    TableDisplay((ClientData) tablePtr);
2625	} else {
2626	    tablePtr->flags |= REDRAW_PENDING;
2627	    Tcl_DoWhenIdle(TableDisplay, (ClientData) tablePtr);
2628	}
2629    }
2630}
2631
2632/*
2633 *----------------------------------------------------------------------
2634 *
2635 * TableFlashEvent --
2636 *	Called when the flash timer goes off.
2637 *
2638 * Results:
2639 *	Decrements all the entries in the hash table and invalidates
2640 *	any cells that expire, deleting them from the table.  If the
2641 *	table is now empty, stops the timer, else reenables it.
2642 *
2643 * Side effects:
2644 *	None.
2645 *
2646 *----------------------------------------------------------------------
2647 */
2648static void
2649TableFlashEvent(ClientData clientdata)
2650{
2651    Table *tablePtr = (Table *) clientdata;
2652    Tcl_HashEntry *entryPtr;
2653    Tcl_HashSearch search;
2654    int entries, count, row, col;
2655
2656    entries = 0;
2657    for (entryPtr = Tcl_FirstHashEntry(tablePtr->flashCells, &search);
2658	 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
2659	count = (int) Tcl_GetHashValue(entryPtr);
2660	if (--count <= 0) {
2661	    /* get the cell address and invalidate that region only */
2662	    TableParseArrayIndex(&row, &col,
2663		    Tcl_GetHashKey(tablePtr->flashCells, entryPtr));
2664
2665	    /* delete the entry from the table */
2666	    Tcl_DeleteHashEntry(entryPtr);
2667
2668	    TableRefresh(tablePtr, row-tablePtr->rowOffset,
2669		    col-tablePtr->colOffset, CELL);
2670	} else {
2671	    Tcl_SetHashValue(entryPtr, (ClientData) count);
2672	    entries++;
2673	}
2674    }
2675
2676    /* do I need to restart the timer */
2677    if (entries && tablePtr->flashMode) {
2678	tablePtr->flashTimer = Tcl_CreateTimerHandler(250, TableFlashEvent,
2679		(ClientData) tablePtr);
2680    } else {
2681	tablePtr->flashTimer = 0;
2682    }
2683}
2684
2685/*
2686 *----------------------------------------------------------------------
2687 *
2688 * TableAddFlash --
2689 *	Adds a flash on cell row,col (real coords) with the default timeout
2690 *	if flashing is enabled and flashtime > 0.
2691 *
2692 * Results:
2693 *	Cell will flash.
2694 *
2695 * Side effects:
2696 *	Will start flash timer if it didn't exist.
2697 *
2698 *----------------------------------------------------------------------
2699 */
2700void
2701TableAddFlash(Table *tablePtr, int row, int col)
2702{
2703    char buf[INDEX_BUFSIZE];
2704    int dummy;
2705    Tcl_HashEntry *entryPtr;
2706
2707    if (!tablePtr->flashMode || tablePtr->flashTime < 1) {
2708	return;
2709    }
2710
2711    /* create the array index in user coords */
2712    TableMakeArrayIndex(row+tablePtr->rowOffset, col+tablePtr->colOffset, buf);
2713
2714    /* add the flash to the hash table */
2715    entryPtr = Tcl_CreateHashEntry(tablePtr->flashCells, buf, &dummy);
2716    Tcl_SetHashValue(entryPtr, tablePtr->flashTime);
2717
2718    /* now set the timer if it's not already going and invalidate the area */
2719    if (tablePtr->flashTimer == NULL) {
2720	tablePtr->flashTimer = Tcl_CreateTimerHandler(250, TableFlashEvent,
2721		(ClientData) tablePtr);
2722    }
2723}
2724
2725/*
2726 *----------------------------------------------------------------------
2727 *
2728 * TableSetActiveIndex --
2729 *	Sets the "active" index of the associated array to the current
2730 *	value of the active buffer.
2731 *
2732 * Results:
2733 *	None.
2734 *
2735 * Side effects:
2736 *	Traces on the array can cause side effects.
2737 *
2738 *----------------------------------------------------------------------
2739 */
2740void
2741TableSetActiveIndex(register Table *tablePtr)
2742{
2743    if (tablePtr->arrayVar) {
2744	tablePtr->flags |= SET_ACTIVE;
2745	Tcl_SetVar2(tablePtr->interp, tablePtr->arrayVar, "active",
2746		tablePtr->activeBuf, TCL_GLOBAL_ONLY);
2747	tablePtr->flags &= ~SET_ACTIVE;
2748    }
2749}
2750
2751/*
2752 *----------------------------------------------------------------------
2753 *
2754 * TableGetActiveBuf --
2755 *	Get the current selection into the buffer and mark it as unedited.
2756 *	Set the position to the end of the string.
2757 *
2758 * Results:
2759 *	None.
2760 *
2761 * Side effects:
2762 *	tablePtr->activeBuf will change.
2763 *
2764 *----------------------------------------------------------------------
2765 */
2766void
2767TableGetActiveBuf(register Table *tablePtr)
2768{
2769    char *data = "";
2770
2771    if (tablePtr->flags & HAS_ACTIVE) {
2772	data = TableGetCellValue(tablePtr,
2773		tablePtr->activeRow+tablePtr->rowOffset,
2774		tablePtr->activeCol+tablePtr->colOffset);
2775    }
2776
2777    if (STREQ(tablePtr->activeBuf, data)) {
2778	/* this forced SetActiveIndex is necessary if we change array vars and
2779	 * they happen to have these cells equal, we won't properly set the
2780	 * active index for the new array var unless we do this here */
2781	TableSetActiveIndex(tablePtr);
2782	return;
2783    }
2784    /* is the buffer long enough */
2785    tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf,
2786	    strlen(data)+1);
2787    strcpy(tablePtr->activeBuf, data);
2788    TableGetIcursor(tablePtr, "end", (int *)0);
2789    tablePtr->flags &= ~TEXT_CHANGED;
2790    TableSetActiveIndex(tablePtr);
2791}
2792
2793/*
2794 *----------------------------------------------------------------------
2795 *
2796 * TableVarProc --
2797 *	This is the trace procedure associated with the Tcl array.  No
2798 *	validation will occur here because this only triggers when the
2799 *	array value is directly set, and we can't maintain the old value.
2800 *
2801 * Results:
2802 *	Invalidates changed cell.
2803 *
2804 * Side effects:
2805 *	Creates/Updates entry in the cache if we are caching.
2806 *
2807 *----------------------------------------------------------------------
2808 */
2809static char *
2810TableVarProc(clientData, interp, name, index, flags)
2811     ClientData clientData;	/* Information about table. */
2812     Tcl_Interp *interp;		/* Interpreter containing variable. */
2813     char *name;			/* Not used. */
2814     char *index;		/* Not used. */
2815     int flags;			/* Information about what happened. */
2816{
2817    Table *tablePtr = (Table *) clientData;
2818    int row, col, update = 1;
2819
2820    /* This is redundant, as the name should always == arrayVar */
2821    name = tablePtr->arrayVar;
2822
2823    /* is this the whole var being destroyed or just one cell being deleted */
2824    if ((flags & TCL_TRACE_UNSETS) && index == NULL) {
2825	/* if this isn't the interpreter being destroyed reinstate the trace */
2826	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
2827	    Tcl_SetVar2(interp, name, TEST_KEY, "", TCL_GLOBAL_ONLY);
2828	    Tcl_UnsetVar2(interp, name, TEST_KEY, TCL_GLOBAL_ONLY);
2829	    Tcl_ResetResult(interp);
2830
2831	    /* set a trace on the variable */
2832	    Tcl_TraceVar(interp, name,
2833		    TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY,
2834		    (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr);
2835
2836	    /* only do the following if arrayVar is our data source */
2837	    if (tablePtr->dataSource & DATA_ARRAY) {
2838		/* clear the selection buffer */
2839		TableGetActiveBuf(tablePtr);
2840		/* flush any cache */
2841		Table_ClearHashTable(tablePtr->cache);
2842		Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS);
2843		/* and invalidate the table */
2844		TableInvalidateAll(tablePtr, 0);
2845	    }
2846	}
2847	return (char *)NULL;
2848    }
2849    /* only continue if arrayVar is our data source */
2850    if (!(tablePtr->dataSource & DATA_ARRAY)) {
2851	return (char *)NULL;
2852    }
2853    /* get the cell address and invalidate that region only.
2854     * Make sure that it is a valid cell address. */
2855    if (STREQ("active", index)) {
2856	if (tablePtr->flags & SET_ACTIVE) {
2857	    /* If we are already setting the active cell, the update
2858	     * will occur in other code */
2859	    update = 0;
2860	} else {
2861	    /* modified TableGetActiveBuf */
2862	    CONST char *data = "";
2863
2864	    row = tablePtr->activeRow;
2865	    col = tablePtr->activeCol;
2866	    if (tablePtr->flags & HAS_ACTIVE)
2867		data = Tcl_GetVar2(interp, name, index, TCL_GLOBAL_ONLY);
2868	    if (!data) data = "";
2869
2870	    if (STREQ(tablePtr->activeBuf, data)) {
2871		return (char *)NULL;
2872	    }
2873	    tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf,
2874		    strlen(data)+1);
2875	    strcpy(tablePtr->activeBuf, data);
2876	    /* set cursor to the last char */
2877	    TableGetIcursor(tablePtr, "end", (int *)0);
2878	    tablePtr->flags |= TEXT_CHANGED;
2879	}
2880    } else if (TableParseArrayIndex(&row, &col, index) == 2) {
2881	char buf[INDEX_BUFSIZE];
2882
2883	/* Make sure it won't trigger on array(2,3extrastuff) */
2884	TableMakeArrayIndex(row, col, buf);
2885	if (strcmp(buf, index)) {
2886	    return (char *)NULL;
2887	}
2888	if (tablePtr->caching) {
2889	    Tcl_HashEntry *entryPtr;
2890	    int new;
2891	    char *val, *data;
2892
2893	    entryPtr = Tcl_CreateHashEntry(tablePtr->cache, buf, &new);
2894	    if (!new) {
2895		data = (char *) Tcl_GetHashValue(entryPtr);
2896		if (data) { ckfree(data); }
2897	    }
2898	    data = (char *) Tcl_GetVar2(interp, name, index, TCL_GLOBAL_ONLY);
2899	    if (data && *data != '\0') {
2900		val = (char *)ckalloc(strlen(data)+1);
2901		strcpy(val, data);
2902	    } else {
2903		val = NULL;
2904	    }
2905	    Tcl_SetHashValue(entryPtr, val);
2906	}
2907	/* convert index to real coords */
2908	row -= tablePtr->rowOffset;
2909	col -= tablePtr->colOffset;
2910	/* did the active cell just update */
2911	if (row == tablePtr->activeRow && col == tablePtr->activeCol) {
2912	    TableGetActiveBuf(tablePtr);
2913	}
2914	/* Flash the cell */
2915	TableAddFlash(tablePtr, row, col);
2916    } else {
2917	return (char *)NULL;
2918    }
2919
2920    if (update) {
2921	TableRefresh(tablePtr, row, col, CELL);
2922    }
2923
2924    return (char *)NULL;
2925}
2926
2927/*
2928 *----------------------------------------------------------------------
2929 *
2930 * TableGeometryRequest --
2931 *	This procedure is invoked to request a new geometry from Tk.
2932 *
2933 * Results:
2934 *	None.
2935 *
2936 * Side effects:
2937 *	Geometry information is updated and a new requested size is
2938 *	registered for the widget.  Internal border info is also set.
2939 *
2940 *----------------------------------------------------------------------
2941 */
2942void
2943TableGeometryRequest(tablePtr)
2944     register Table *tablePtr;
2945{
2946    int x, y;
2947
2948    /* Do the geometry request
2949     * If -width #cols was not specified or it is greater than the real
2950     * number of cols, use maxWidth as a lower bound, with the other lower
2951     * bound being the upper bound of the window's user-set width and the
2952     * value of -maxwidth set by the programmer
2953     * Vice versa for rows/height
2954     */
2955    x = MIN((tablePtr->maxReqCols==0 || tablePtr->maxReqCols > tablePtr->cols)?
2956	    tablePtr->maxWidth : tablePtr->colStarts[tablePtr->maxReqCols],
2957	    tablePtr->maxReqWidth) + 2*tablePtr->highlightWidth;
2958    y = MIN((tablePtr->maxReqRows==0 || tablePtr->maxReqRows > tablePtr->rows)?
2959	    tablePtr->maxHeight : tablePtr->rowStarts[tablePtr->maxReqRows],
2960	    tablePtr->maxReqHeight) + 2*tablePtr->highlightWidth;
2961    Tk_GeometryRequest(tablePtr->tkwin, x, y);
2962}
2963
2964/*
2965 *----------------------------------------------------------------------
2966 *
2967 * TableAdjustActive --
2968 *	This procedure is called by AdjustParams and CMD_ACTIVATE to
2969 *	move the active cell.
2970 *
2971 * Results:
2972 *	Old and new active cell indices will be invalidated.
2973 *
2974 * Side effects:
2975 *	If the old active cell index was edited, it will be saved.
2976 *	The active buffer will be updated.
2977 *
2978 *----------------------------------------------------------------------
2979 */
2980void
2981TableAdjustActive(tablePtr)
2982     register Table *tablePtr;		/* Widget record for table */
2983{
2984    if (tablePtr->flags & HAS_ACTIVE) {
2985	/*
2986	 * Make sure the active cell has a reasonable real index
2987	 */
2988	CONSTRAIN(tablePtr->activeRow, 0, tablePtr->rows-1);
2989	CONSTRAIN(tablePtr->activeCol, 0, tablePtr->cols-1);
2990    }
2991
2992    /*
2993     * Check the new value of active cell against the original,
2994     * Only invalidate if it changed.
2995     */
2996    if (tablePtr->oldActRow == tablePtr->activeRow &&
2997	    tablePtr->oldActCol == tablePtr->activeCol) {
2998	return;
2999    }
3000
3001    if (tablePtr->oldActRow >= 0 && tablePtr->oldActCol >= 0) {
3002	/*
3003	 * Set the value of the old active cell to the active buffer
3004	 * SetCellValue will check if the value actually changed
3005	 */
3006	if (tablePtr->flags & TEXT_CHANGED) {
3007	    /* WARNING an outside trace will be triggered here and if it
3008	     * calls something that causes TableAdjustParams to be called
3009	     * again, we are in data consistency trouble */
3010	    /* HACK - turn TEXT_CHANGED off now to possibly avoid the
3011	     * above data inconsistency problem.  */
3012	    tablePtr->flags &= ~TEXT_CHANGED;
3013	    TableSetCellValue(tablePtr,
3014		    tablePtr->oldActRow + tablePtr->rowOffset,
3015		    tablePtr->oldActCol + tablePtr->colOffset,
3016		    tablePtr->activeBuf);
3017	}
3018	/*
3019	 * Invalidate the old active cell
3020	 */
3021	TableRefresh(tablePtr, tablePtr->oldActRow, tablePtr->oldActCol, CELL);
3022    }
3023
3024    /*
3025     * Store the new active cell value into the active buffer
3026     */
3027    TableGetActiveBuf(tablePtr);
3028
3029    /*
3030     * Invalidate the new active cell
3031     */
3032    TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL);
3033
3034    /*
3035     * Cache the old active row/col for the next time this is called
3036     */
3037    tablePtr->oldActRow = tablePtr->activeRow;
3038    tablePtr->oldActCol = tablePtr->activeCol;
3039}
3040
3041/*
3042 *----------------------------------------------------------------------
3043 *
3044 * TableAdjustParams --
3045 *	Calculate the row and column starts.  Adjusts the topleft corner
3046 *	variable to keep it within the screen range, out of the titles
3047 *	and keep the screen full make sure the selected cell is in the
3048 *	visible area checks to see if the top left cell has changed at
3049 *	all and invalidates the table if it has.
3050 *
3051 * Results:
3052 *	None.
3053 *
3054 * Side Effects:
3055 *	Number of rows can change if -rowstretchmode == fill.
3056 *	topRow && leftCol can change to fit display.
3057 *	activeRow/Col can change to ensure it is a valid cell.
3058 *
3059 *----------------------------------------------------------------------
3060 */
3061void
3062TableAdjustParams(register Table *tablePtr)
3063{
3064    int topRow, leftCol, row, col, total, i, value, x, y, width, height,
3065	w, h, hl, px, py, recalc, bd[4],
3066	diff, unpreset, lastUnpreset, pad, lastPad, numPixels,
3067	defColWidth, defRowHeight;
3068    Tcl_HashEntry *entryPtr;
3069
3070    /*
3071     * Cache some values for many upcoming calculations
3072     */
3073    hl = tablePtr->highlightWidth;
3074    w  = Tk_Width(tablePtr->tkwin) - (2 * hl);
3075    h  = Tk_Height(tablePtr->tkwin) - (2 * hl);
3076    TableGetTagBorders(&(tablePtr->defaultTag),
3077	    &bd[0], &bd[1], &bd[2], &bd[3]);
3078    px = bd[0] + bd[1] + (2 * tablePtr->padX);
3079    py = bd[2] + bd[3] + (2 * tablePtr->padY);
3080
3081    /*
3082     * Account for whether default dimensions are in chars (>0) or
3083     * pixels (<=0).  Border and Pad space is added in here for convenience.
3084     *
3085     * When a value in pixels is specified, we take that exact amount,
3086     * not adding in padding.
3087     */
3088    if (tablePtr->defColWidth > 0) {
3089	defColWidth = tablePtr->charWidth * tablePtr->defColWidth + px;
3090    } else {
3091	defColWidth = -(tablePtr->defColWidth);
3092    }
3093    if (tablePtr->defRowHeight > 0) {
3094	defRowHeight = tablePtr->charHeight * tablePtr->defRowHeight + py;
3095    } else {
3096	defRowHeight = -(tablePtr->defRowHeight);
3097    }
3098
3099    /*
3100     * Set up the arrays to hold the col pixels and starts.
3101     * ckrealloc was fixed in 8.2.1 to handle NULLs, so we can't rely on it.
3102     */
3103    if (tablePtr->colPixels) ckfree((char *) tablePtr->colPixels);
3104    tablePtr->colPixels = (int *) ckalloc(tablePtr->cols * sizeof(int));
3105    if (tablePtr->colStarts) ckfree((char *) tablePtr->colStarts);
3106    tablePtr->colStarts = (int *) ckalloc((tablePtr->cols+1) * sizeof(int));
3107
3108    /*
3109     * Get all the preset columns and set their widths
3110     */
3111    lastUnpreset = 0;
3112    numPixels = 0;
3113    unpreset = 0;
3114    for (i = 0; i < tablePtr->cols; i++) {
3115	entryPtr = Tcl_FindHashEntry(tablePtr->colWidths, (char *) i);
3116	if (entryPtr == NULL) {
3117	    tablePtr->colPixels[i] = -1;
3118	    unpreset++;
3119	    lastUnpreset = i;
3120	} else {
3121	    value = (int) Tcl_GetHashValue(entryPtr);
3122	    if (value > 0) {
3123		tablePtr->colPixels[i] = value * tablePtr->charWidth + px;
3124	    } else {
3125		/*
3126		 * When a value in pixels is specified, we take that exact
3127		 * amount, not adding in pad or border values.
3128		 */
3129		tablePtr->colPixels[i] = -value;
3130	    }
3131	    numPixels += tablePtr->colPixels[i];
3132	}
3133    }
3134
3135    /*
3136     * Work out how much to pad each col depending on the mode.
3137     */
3138    diff  = w - numPixels - (unpreset * defColWidth);
3139    total = 0;
3140
3141    /*
3142     * Now do the padding and calculate the column starts.
3143     * Diff lower than 0 means we can't see the entire set of columns,
3144     * thus no special stretching will occur & we optimize the calculation.
3145     */
3146    if (diff <= 0) {
3147	for (i = 0; i < tablePtr->cols; i++) {
3148	    if (tablePtr->colPixels[i] == -1) {
3149		tablePtr->colPixels[i] = defColWidth;
3150	    }
3151	    tablePtr->colStarts[i] = total;
3152	    total += tablePtr->colPixels[i];
3153	}
3154    } else {
3155	switch (tablePtr->colStretch) {
3156	case STRETCH_MODE_NONE:
3157	    pad		= 0;
3158	    lastPad	= 0;
3159	    break;
3160	case STRETCH_MODE_UNSET:
3161	    if (unpreset == 0) {
3162		pad	= 0;
3163		lastPad	= 0;
3164	    } else {
3165		pad	= diff / unpreset;
3166		lastPad	= diff - pad * (unpreset - 1);
3167	    }
3168	    break;
3169	case STRETCH_MODE_LAST:
3170	    pad		= 0;
3171	    lastPad	= diff;
3172	    lastUnpreset = tablePtr->cols - 1;
3173	    break;
3174	default:	/* STRETCH_MODE_ALL, but also FILL for cols */
3175	    pad		= diff / tablePtr->cols;
3176	    /* force it to be applied to the last column too */
3177	    lastUnpreset = tablePtr->cols - 1;
3178	    lastPad	= diff - pad * lastUnpreset;
3179	}
3180
3181	for (i = 0; i < tablePtr->cols; i++) {
3182	    if (tablePtr->colPixels[i] == -1) {
3183		tablePtr->colPixels[i] = defColWidth
3184		    + ((i == lastUnpreset) ? lastPad : pad);
3185	    } else if (tablePtr->colStretch == STRETCH_MODE_ALL) {
3186		tablePtr->colPixels[i] += (i == lastUnpreset) ? lastPad : pad;
3187	    }
3188	    tablePtr->colStarts[i] = total;
3189	    total += tablePtr->colPixels[i];
3190	}
3191    }
3192    tablePtr->colStarts[i] = tablePtr->maxWidth = total;
3193
3194    /*
3195     * The 'do' loop is only necessary for rows because of FILL mode
3196     */
3197    recalc = 0;
3198    do {
3199	/* Set up the arrays to hold the row pixels and starts */
3200	/* FIX - this can be moved outside 'do' if you check >row size */
3201	if (tablePtr->rowPixels) ckfree((char *) tablePtr->rowPixels);
3202	tablePtr->rowPixels = (int *) ckalloc(tablePtr->rows * sizeof(int));
3203
3204	/* get all the preset rows and set their heights */
3205	lastUnpreset	= 0;
3206	numPixels	= 0;
3207	unpreset	= 0;
3208	for (i = 0; i < tablePtr->rows; i++) {
3209	    entryPtr = Tcl_FindHashEntry(tablePtr->rowHeights, (char *) i);
3210	    if (entryPtr == NULL) {
3211		tablePtr->rowPixels[i] = -1;
3212		unpreset++;
3213		lastUnpreset = i;
3214	    } else {
3215		value = (int) Tcl_GetHashValue(entryPtr);
3216		if (value > 0) {
3217		    tablePtr->rowPixels[i] = value * tablePtr->charHeight + py;
3218		} else {
3219		    /*
3220		     * When a value in pixels is specified, we take that exact
3221		     * amount, not adding in pad or border values.
3222		     */
3223		    tablePtr->rowPixels[i] = -value;
3224		}
3225		numPixels += tablePtr->rowPixels[i];
3226	    }
3227	}
3228
3229	/* work out how much to pad each row depending on the mode */
3230	diff = h - numPixels - (unpreset * defRowHeight);
3231	switch(tablePtr->rowStretch) {
3232	case STRETCH_MODE_NONE:
3233	    pad		= 0;
3234	    lastPad	= 0;
3235	    break;
3236	case STRETCH_MODE_UNSET:
3237	    if (unpreset == 0)  {
3238		pad	= 0;
3239		lastPad	= 0;
3240	    } else {
3241		pad	= MAX(0,diff) / unpreset;
3242		lastPad	= MAX(0,diff) - pad * (unpreset - 1);
3243	    }
3244	    break;
3245	case STRETCH_MODE_LAST:
3246	    pad		= 0;
3247	    lastPad	= MAX(0,diff);
3248	    /* force it to be applied to the last column too */
3249	    lastUnpreset = tablePtr->rows - 1;
3250	    break;
3251	case STRETCH_MODE_FILL:
3252	    pad		= 0;
3253	    lastPad	= diff;
3254	    if (diff && !recalc) {
3255		tablePtr->rows += (diff/defRowHeight);
3256		if (diff < 0 && tablePtr->rows <= 0) {
3257		    tablePtr->rows = 1;
3258		}
3259		lastUnpreset = tablePtr->rows - 1;
3260		recalc = 1;
3261		continue;
3262	    } else {
3263		lastUnpreset = tablePtr->rows - 1;
3264		recalc = 0;
3265	    }
3266	    break;
3267	default:	/* STRETCH_MODE_ALL */
3268	    pad		= MAX(0,diff) / tablePtr->rows;
3269	    /* force it to be applied to the last column too */
3270	    lastUnpreset = tablePtr->rows - 1;
3271	    lastPad	= MAX(0,diff) - pad * lastUnpreset;
3272	}
3273    } while (recalc);
3274
3275    if (tablePtr->rowStarts) ckfree((char *) tablePtr->rowStarts);
3276    tablePtr->rowStarts = (int *) ckalloc((tablePtr->rows+1)*sizeof(int));
3277    /*
3278     * Now do the padding and calculate the row starts
3279     */
3280    total = 0;
3281    for (i = 0; i < tablePtr->rows; i++) {
3282	if (tablePtr->rowPixels[i] == -1) {
3283	    tablePtr->rowPixels[i] = defRowHeight
3284		+ ((i==lastUnpreset)?lastPad:pad);
3285	} else if (tablePtr->rowStretch == STRETCH_MODE_ALL) {
3286	    tablePtr->rowPixels[i] += (i==lastUnpreset)?lastPad:pad;
3287	}
3288	/* calculate the start of each row */
3289	tablePtr->rowStarts[i] = total;
3290	total += tablePtr->rowPixels[i];
3291    }
3292    tablePtr->rowStarts[i] = tablePtr->maxHeight = total;
3293
3294    /*
3295     * Make sure the top row and col have reasonable real indices
3296     */
3297    CONSTRAIN(tablePtr->topRow, tablePtr->titleRows, tablePtr->rows-1);
3298    CONSTRAIN(tablePtr->leftCol, tablePtr->titleCols, tablePtr->cols-1);
3299
3300    /*
3301     * If we don't have the info, don't bother to fix up the other parameters
3302     */
3303    if (Tk_WindowId(tablePtr->tkwin) == None) {
3304	tablePtr->oldTopRow = tablePtr->oldLeftCol = -1;
3305	return;
3306    }
3307
3308    topRow  = tablePtr->topRow;
3309    leftCol = tablePtr->leftCol;
3310    w += hl;
3311    h += hl;
3312    /*
3313     * If we use this value of topRow, will we fill the window?
3314     * if not, decrease it until we will, or until it gets to titleRows
3315     * make sure we don't cut off the bottom row
3316     */
3317    for (; topRow > tablePtr->titleRows; topRow--) {
3318	if ((tablePtr->maxHeight-(tablePtr->rowStarts[topRow-1] -
3319		tablePtr->rowStarts[tablePtr->titleRows])) > h) {
3320	    break;
3321	}
3322    }
3323    /*
3324     * If we use this value of topCol, will we fill the window?
3325     * if not, decrease it until we will, or until it gets to titleCols
3326     * make sure we don't cut off the left column
3327     */
3328    for (; leftCol > tablePtr->titleCols; leftCol--) {
3329	if ((tablePtr->maxWidth-(tablePtr->colStarts[leftCol-1] -
3330		tablePtr->colStarts[tablePtr->titleCols])) > w) {
3331	    break;
3332	}
3333    }
3334
3335    tablePtr->topRow  = topRow;
3336    tablePtr->leftCol = leftCol;
3337
3338    /*
3339     * Now work out where the bottom right is for scrollbar update and to test
3340     * for one last stretch.  Avoid the confusion that spans could cause for
3341     * determining the last cell dimensions.
3342     */
3343    tablePtr->flags |= AVOID_SPANS;
3344    TableGetLastCell(tablePtr, &row, &col);
3345    TableCellVCoords(tablePtr, row, col, &x, &y, &width, &height, 0);
3346    tablePtr->flags &= ~AVOID_SPANS;
3347
3348    /*
3349     * Do we have scrollbars, if so, calculate and call the TCL functions In
3350     * order to get the scrollbar to be completely full when the whole screen
3351     * is shown and there are titles, we have to arrange for the scrollbar
3352     * range to be 0 -> rows-titleRows etc.  This leads to the position
3353     * setting methods, toprow and leftcol, being relative to the titles, not
3354     * absolute row and column numbers.
3355     */
3356    if (tablePtr->yScrollCmd != NULL || tablePtr->xScrollCmd != NULL) {
3357	Tcl_Interp *interp = tablePtr->interp;
3358	char buf[INDEX_BUFSIZE];
3359	double first, last;
3360
3361	/*
3362	 * We must hold onto the interpreter because the data referred to at
3363	 * tablePtr might be freed as a result of the call to Tcl_VarEval.
3364	 */
3365	Tcl_Preserve((ClientData) interp);
3366
3367	/* Do we have a Y-scrollbar and rows to scroll? */
3368	if (tablePtr->yScrollCmd != NULL) {
3369	    if (row < tablePtr->titleRows) {
3370		first = 0;
3371		last  = 1;
3372	    } else {
3373		diff = tablePtr->rowStarts[tablePtr->titleRows];
3374		last = (double) (tablePtr->rowStarts[tablePtr->rows]-diff);
3375		if (last <= 0.0) {
3376		    first = 0;
3377		    last  = 1;
3378		} else {
3379		    first = (tablePtr->rowStarts[topRow]-diff) / last;
3380		    last  = (height+tablePtr->rowStarts[row]-diff) / last;
3381		}
3382	    }
3383	    sprintf(buf, " %g %g", first, last);
3384	    if (Tcl_VarEval(interp, tablePtr->yScrollCmd,
3385		    buf, (char *)NULL) != TCL_OK) {
3386		Tcl_AddErrorInfo(interp,
3387			"\n\t(vertical scrolling command executed by table)");
3388		Tcl_BackgroundError(interp);
3389	    }
3390	}
3391	/* Do we have a X-scrollbar and cols to scroll? */
3392	if (tablePtr->xScrollCmd != NULL) {
3393	    if (col < tablePtr->titleCols) {
3394		first = 0;
3395		last  = 1;
3396	    } else {
3397		diff = tablePtr->colStarts[tablePtr->titleCols];
3398		last = (double) (tablePtr->colStarts[tablePtr->cols]-diff);
3399		if (last <= 0.0) {
3400		    first = 0;
3401		    last  = 1;
3402		} else {
3403		    first = (tablePtr->colStarts[leftCol]-diff) / last;
3404		    last  = (width+tablePtr->colStarts[col]-diff) / last;
3405		}
3406	    }
3407	    sprintf(buf, " %g %g", first, last);
3408	    if (Tcl_VarEval(interp, tablePtr->xScrollCmd,
3409		    buf, (char *)NULL) != TCL_OK) {
3410		Tcl_AddErrorInfo(interp,
3411			"\n\t(horizontal scrolling command executed by table)");
3412		Tcl_BackgroundError(interp);
3413	    }
3414	}
3415
3416	Tcl_Release((ClientData) interp);
3417    }
3418
3419    /*
3420     * Adjust the last row/col to fill empty space if it is visible.
3421     * Do this after setting the scrollbars to not upset its calculations.
3422     */
3423    if (row == tablePtr->rows-1 && tablePtr->rowStretch != STRETCH_MODE_NONE) {
3424	diff = h-(y+height);
3425	if (diff > 0) {
3426	    tablePtr->rowPixels[tablePtr->rows-1] += diff;
3427	    tablePtr->rowStarts[tablePtr->rows] += diff;
3428	}
3429    }
3430    if (col == tablePtr->cols-1 && tablePtr->colStretch != STRETCH_MODE_NONE) {
3431	diff = w-(x+width);
3432	if (diff > 0) {
3433	    tablePtr->colPixels[tablePtr->cols-1] += diff;
3434	    tablePtr->colStarts[tablePtr->cols] += diff;
3435	}
3436    }
3437
3438    TableAdjustActive(tablePtr);
3439
3440    /*
3441     * now check the new value of topleft cell against the originals,
3442     * If they changed, invalidate the area, else leave it alone
3443     */
3444    if (tablePtr->topRow != tablePtr->oldTopRow ||
3445	tablePtr->leftCol != tablePtr->oldLeftCol) {
3446	/* set the old top row/col for the next time this function is called */
3447	tablePtr->oldTopRow = tablePtr->topRow;
3448	tablePtr->oldLeftCol = tablePtr->leftCol;
3449	/* only the upper corner title cells wouldn't change */
3450	TableInvalidateAll(tablePtr, 0);
3451    }
3452}
3453
3454/*
3455 *----------------------------------------------------------------------
3456 *
3457 * TableCursorEvent --
3458 *	Toggle the cursor status.  Equivalent to EntryBlinkProc.
3459 *
3460 * Results:
3461 *	None.
3462 *
3463 * Side effects:
3464 *	The cursor will be switched off/on.
3465 *
3466 *----------------------------------------------------------------------
3467 */
3468static void
3469TableCursorEvent(ClientData clientData)
3470{
3471    register Table *tablePtr = (Table *) clientData;
3472
3473    if (!(tablePtr->flags & HAS_FOCUS) || (tablePtr->insertOffTime == 0)
3474	    || (tablePtr->flags & ACTIVE_DISABLED)
3475	    || (tablePtr->state != STATE_NORMAL)) {
3476	return;
3477    }
3478
3479    if (tablePtr->cursorTimer != NULL) {
3480	Tcl_DeleteTimerHandler(tablePtr->cursorTimer);
3481    }
3482
3483    tablePtr->cursorTimer =
3484	Tcl_CreateTimerHandler((tablePtr->flags & CURSOR_ON) ?
3485		tablePtr->insertOffTime : tablePtr->insertOnTime,
3486		TableCursorEvent, (ClientData) tablePtr);
3487
3488    /* Toggle the cursor */
3489    tablePtr->flags ^= CURSOR_ON;
3490
3491    /* invalidate the cell */
3492    TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL);
3493}
3494
3495/*
3496 *----------------------------------------------------------------------
3497 *
3498 * TableConfigCursor --
3499 *	Configures the timer depending on the state of the table.
3500 *	Equivalent to EntryFocusProc.
3501 *
3502 * Results:
3503 *	None.
3504 *
3505 * Side effects:
3506 *	The cursor will be switched off/on.
3507 *
3508 *----------------------------------------------------------------------
3509 */
3510void
3511TableConfigCursor(register Table *tablePtr)
3512{
3513    /*
3514     * To have a cursor, we have to have focus and allow edits
3515     */
3516    if ((tablePtr->flags & HAS_FOCUS) && (tablePtr->state == STATE_NORMAL) &&
3517	!(tablePtr->flags & ACTIVE_DISABLED)) {
3518	/*
3519	 * Turn the cursor ON
3520	 */
3521	if (!(tablePtr->flags & CURSOR_ON)) {
3522	    tablePtr->flags |= CURSOR_ON;
3523	    /*
3524	     * Only refresh when we toggled cursor
3525	     */
3526	    TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol,
3527		    CELL);
3528	}
3529
3530	/* set up the first timer */
3531	if (tablePtr->insertOffTime != 0) {
3532	    /* make sure nothing existed */
3533	    Tcl_DeleteTimerHandler(tablePtr->cursorTimer);
3534	    tablePtr->cursorTimer =
3535		Tcl_CreateTimerHandler(tablePtr->insertOnTime,
3536			TableCursorEvent, (ClientData) tablePtr);
3537	}
3538    } else {
3539	/*
3540	 * Turn the cursor OFF
3541	 */
3542	if ((tablePtr->flags & CURSOR_ON)) {
3543	    tablePtr->flags &= ~CURSOR_ON;
3544	    TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol,
3545		    CELL);
3546	}
3547
3548	/* and disable the timer */
3549	if (tablePtr->cursorTimer != NULL) {
3550	    Tcl_DeleteTimerHandler(tablePtr->cursorTimer);
3551	}
3552	tablePtr->cursorTimer = NULL;
3553    }
3554
3555}
3556
3557/*
3558 *----------------------------------------------------------------------
3559 *
3560 * TableFetchSelection --
3561 *	This procedure is called back by Tk when the selection is
3562 *	requested by someone.  It returns part or all of the selection
3563 *	in a buffer provided by the caller.
3564 *
3565 * Results:
3566 *	The return value is the number of non-NULL bytes stored
3567 *	at buffer.  Buffer is filled (or partially filled) with a
3568 *	NULL-terminated string containing part or all of the selection,
3569 *	as given by offset and maxBytes.
3570 *
3571 * Side effects:
3572 *	None.
3573 *
3574 *----------------------------------------------------------------------
3575 */
3576
3577static int
3578TableFetchSelection(clientData, offset, buffer, maxBytes)
3579     ClientData clientData;	/* Information about table widget. */
3580     int offset;		/* Offset within selection of first
3581				 * character to be returned. */
3582     char *buffer;		/* Location in which to place selection. */
3583     int maxBytes;		/* Maximum number of bytes to place at buffer,
3584				 * not including terminating NULL. */
3585{
3586    register Table *tablePtr = (Table *) clientData;
3587    Tcl_Interp *interp = tablePtr->interp;
3588    char *value, *data, *rowsep = tablePtr->rowSep, *colsep = tablePtr->colSep;
3589    Tcl_HashEntry *entryPtr;
3590    Tcl_HashSearch search;
3591    int length, count, lastrow=0, needcs=0, r, c, listArgc, rslen=0, cslen=0;
3592    int numcols, numrows;
3593    CONST84 char **listArgv;
3594
3595    /*
3596     * We keep a static selection around so we don't have to remake the
3597     * selection if we are getting the selection in chunks (i.e. offset != 0).
3598     * Not thread-safe, but selection happens sequentially in practice.
3599     * Otherwise could move them to per-table, but then more cleanup and
3600     * tracking is needed (flag bit + extended table struct).
3601     */
3602    static int haveSelection = 0;
3603    static Tcl_DString selection;
3604
3605    /* if we are not exporting the selection ||
3606     * we have no data source, return */
3607    if (!tablePtr->exportSelection ||
3608	(tablePtr->dataSource == DATA_NONE)) {
3609	return -1;
3610    }
3611
3612    if ((offset == 0) || !haveSelection) {
3613	/* First Time thru, get the selection, otherwise, just use the
3614	 * selection obtained before */
3615
3616	if (haveSelection) {
3617	    /* If we have fetched a selection before, free it */
3618	    Tcl_DStringFree(&selection);
3619	}
3620	haveSelection = 1;
3621
3622	/* First get a sorted list of the selected elements */
3623	Tcl_DStringInit(&selection);
3624	for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search);
3625	     entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
3626	    Tcl_DStringAppendElement(&selection,
3627		    Tcl_GetHashKey(tablePtr->selCells, entryPtr));
3628	}
3629	value = TableCellSort(tablePtr, Tcl_DStringValue(&selection));
3630	Tcl_DStringFree(&selection);
3631
3632	if (value == NULL ||
3633		Tcl_SplitList(interp, value, &listArgc, &listArgv) != TCL_OK) {
3634	    return -1;
3635	}
3636	Tcl_Free(value);
3637
3638	Tcl_DStringInit(&selection);
3639	rslen = (rowsep?(strlen(rowsep)):0);
3640	cslen = (colsep?(strlen(colsep)):0);
3641	numrows = numcols = 0;
3642	for (count = 0; count < listArgc; count++) {
3643	    TableParseArrayIndex(&r, &c, listArgv[count]);
3644	    if (count) {
3645		if (lastrow != r) {
3646		    lastrow = r;
3647		    needcs = 0;
3648		    if (rslen) {
3649			Tcl_DStringAppend(&selection, rowsep, rslen);
3650		    } else {
3651			Tcl_DStringEndSublist(&selection);
3652			Tcl_DStringStartSublist(&selection);
3653		    }
3654		    ++numrows;
3655		} else {
3656		    if (++needcs > numcols)
3657			numcols = needcs;
3658		}
3659	    } else {
3660		lastrow = r;
3661		needcs = 0;
3662		if (!rslen) {
3663		    Tcl_DStringStartSublist(&selection);
3664		}
3665	    }
3666	    data = TableGetCellValue(tablePtr, r, c);
3667	    if (cslen) {
3668		if (needcs) {
3669		    Tcl_DStringAppend(&selection, colsep, cslen);
3670		}
3671		Tcl_DStringAppend(&selection, data, -1);
3672	    } else {
3673		Tcl_DStringAppendElement(&selection, data);
3674	    }
3675	}
3676	if (!rslen && count) {
3677	    Tcl_DStringEndSublist(&selection);
3678	}
3679	Tcl_Free((char *) listArgv);
3680
3681	if (tablePtr->selCmd != NULL) {
3682	    Tcl_DString script;
3683	    Tcl_DStringInit(&script);
3684	    ExpandPercents(tablePtr, tablePtr->selCmd, numrows+1, numcols+1,
3685		    Tcl_DStringValue(&selection), (char *)NULL,
3686		    listArgc, &script, CMD_ACTIVATE);
3687	    if (Tcl_EvalEx(interp, Tcl_DStringValue(&script), -1,
3688			TCL_EVAL_GLOBAL) == TCL_ERROR) {
3689		Tcl_AddErrorInfo(interp,
3690			"\n    (error in table selection command)");
3691		Tcl_BackgroundError(interp);
3692		Tcl_DStringFree(&script);
3693		Tcl_DStringFree(&selection);
3694		haveSelection = 0;
3695		return -1;
3696	    } else {
3697		Tcl_DStringGetResult(interp, &selection);
3698	    }
3699	    Tcl_DStringFree(&script);
3700	}
3701    }
3702    length = Tcl_DStringLength(&selection);
3703
3704    if (length == 0) {
3705	return -1;
3706    }
3707
3708    /* Copy the requested portion of the selection to the buffer. */
3709    count = length - offset;
3710    if (count <= 0) {
3711	count = 0;
3712    } else {
3713	if (count > maxBytes) {
3714	    count = maxBytes;
3715	}
3716	memcpy((VOID *) buffer,
3717	       (VOID *) (Tcl_DStringValue(&selection) + offset),
3718	       (size_t) count);
3719    }
3720    buffer[count] = '\0';
3721    if (count < maxBytes) {
3722	/*
3723	 * This should be the last call in this range, so free selection now.
3724	 */
3725	Tcl_DStringFree(&selection);
3726	haveSelection = 0;
3727    }
3728    return count;
3729}
3730
3731/*
3732 *----------------------------------------------------------------------
3733 *
3734 * TableLostSelection --
3735 *	This procedure is called back by Tk when the selection is
3736 *	grabbed away from a table widget.
3737 *
3738 * Results:
3739 *	None.
3740 *
3741 * Side effects:
3742 *	The existing selection is unhighlighted, and the window is
3743 *	marked as not containing a selection.
3744 *
3745 *----------------------------------------------------------------------
3746 */
3747void
3748TableLostSelection(clientData)
3749     ClientData clientData;	/* Information about table widget. */
3750{
3751    register Table *tablePtr = (Table *) clientData;
3752
3753    if (tablePtr->exportSelection) {
3754	Tcl_HashEntry *entryPtr;
3755	Tcl_HashSearch search;
3756	int row, col;
3757
3758	/* Same as SEL CLEAR ALL */
3759	for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search);
3760	     entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
3761	    TableParseArrayIndex(&row, &col,
3762				 Tcl_GetHashKey(tablePtr->selCells,entryPtr));
3763	    Tcl_DeleteHashEntry(entryPtr);
3764	    TableRefresh(tablePtr, row-tablePtr->rowOffset,
3765			 col-tablePtr->colOffset, CELL);
3766	}
3767    }
3768}
3769
3770/*
3771 *----------------------------------------------------------------------
3772 *
3773 * TableRestrictProc --
3774 *	A Tk_RestrictProc used by TableValidateChange to eliminate any
3775 *	extra key input events in the event queue that
3776 *	have a serial number no less than a given value.
3777 *
3778 * Results:
3779 *	Returns either TK_DISCARD_EVENT or TK_DEFER_EVENT.
3780 *
3781 * Side effects:
3782 *	None.
3783 *
3784 *----------------------------------------------------------------------
3785 */
3786static Tk_RestrictAction
3787TableRestrictProc(serial, eventPtr)
3788     ClientData serial;
3789     XEvent *eventPtr;
3790{
3791    if ((eventPtr->type == KeyRelease || eventPtr->type == KeyPress) &&
3792	((eventPtr->xany.serial-(unsigned int)serial) > 0)) {
3793	return TK_DEFER_EVENT;
3794    } else {
3795	return TK_PROCESS_EVENT;
3796    }
3797}
3798
3799/*
3800 *--------------------------------------------------------------
3801 *
3802 * TableValidateChange --
3803 *	This procedure is invoked when any character is added or
3804 *	removed from the table widget, or a set has triggered validation.
3805 *
3806 * Results:
3807 *	TCL_OK    if the validatecommand accepts the new string,
3808 *	TCL_BREAK if the validatecommand rejects the new string,
3809 *      TCL_ERROR if any problems occured with validatecommand.
3810 *
3811 * Side effects:
3812 *      The insertion/deletion may be aborted, and the
3813 *      validatecommand might turn itself off (if an error
3814 *      or loop condition arises).
3815 *
3816 *--------------------------------------------------------------
3817 */
3818int
3819TableValidateChange(tablePtr, r, c, old, new, index)
3820     register Table *tablePtr;	/* Table that needs validation. */
3821     int r, c;			/* row,col index of cell in user coords */
3822     char *old;			/* current value of cell */
3823     char *new;			/* potential new value of cell */
3824     int index;			/* index of insert/delete, -1 otherwise */
3825{
3826    register Tcl_Interp *interp = tablePtr->interp;
3827    int code, bool;
3828    Tk_RestrictProc *rstrct;
3829    ClientData cdata;
3830    Tcl_DString script;
3831
3832    if (tablePtr->valCmd == NULL || tablePtr->validate == 0) {
3833	return TCL_OK;
3834    }
3835
3836    /* Magic code to make this bit of code UI synchronous in the face of
3837     * possible new key events */
3838    XSync(tablePtr->display, False);
3839    rstrct = Tk_RestrictEvents(TableRestrictProc, (ClientData)
3840				 NextRequest(tablePtr->display), &cdata);
3841
3842    /*
3843     * If we're already validating, then we're hitting a loop condition
3844     * Return and set validate to 0 to disallow further validations
3845     * and prevent current validation from finishing
3846     */
3847    if (tablePtr->flags & VALIDATING) {
3848	tablePtr->validate = 0;
3849	return TCL_OK;
3850    }
3851    tablePtr->flags |= VALIDATING;
3852
3853    /* Now form command string and run through the -validatecommand */
3854    Tcl_DStringInit(&script);
3855    ExpandPercents(tablePtr, tablePtr->valCmd, r, c, old, new, index, &script,
3856		   CMD_VALIDATE);
3857    code = Tcl_GlobalEval(tablePtr->interp, Tcl_DStringValue(&script));
3858    Tcl_DStringFree(&script);
3859
3860    if (code != TCL_OK && code != TCL_RETURN) {
3861	Tcl_AddErrorInfo(interp,
3862			 "\n\t(in validation command executed by table)");
3863	Tcl_BackgroundError(interp);
3864	code = TCL_ERROR;
3865    } else if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp),
3866				     &bool) != TCL_OK) {
3867	Tcl_AddErrorInfo(interp,
3868			 "\n\tboolean not returned by validation command");
3869	Tcl_BackgroundError(interp);
3870	code = TCL_ERROR;
3871    } else {
3872	code = (bool) ? TCL_OK : TCL_BREAK;
3873    }
3874    Tcl_SetObjResult(interp, Tcl_NewObj());
3875
3876    /*
3877     * If ->validate has become VALIDATE_NONE during the validation,
3878     * it means that a loop condition almost occured.  Do not allow
3879     * this validation result to finish.
3880     */
3881    if (tablePtr->validate == 0) {
3882	code = TCL_ERROR;
3883    }
3884
3885    /* If validate will return ERROR, then disallow further validations */
3886    if (code == TCL_ERROR) {
3887	tablePtr->validate = 0;
3888    }
3889
3890    Tk_RestrictEvents(rstrct, cdata, &cdata);
3891    tablePtr->flags &= ~VALIDATING;
3892
3893    return code;
3894}
3895
3896/*
3897 *--------------------------------------------------------------
3898 *
3899 * ExpandPercents --
3900 *	Given a command and an event, produce a new command
3901 *	by replacing % constructs in the original command
3902 *	with information from the X event.
3903 *
3904 * Results:
3905 *	The new expanded command is appended to the dynamic string
3906 *	given by dsPtr.
3907 *
3908 * Side effects:
3909 *	None.
3910 *
3911 *--------------------------------------------------------------
3912 */
3913void
3914ExpandPercents(tablePtr, before, r, c, old, new, index, dsPtr, cmdType)
3915     Table *tablePtr;		/* Table that needs validation. */
3916     char *before;		/* Command containing percent
3917				 * expressions to be replaced. */
3918     int r, c;			/* row,col index of cell */
3919     char *old;                 /* current value of cell */
3920     char *new;                 /* potential new value of cell */
3921     int index;                 /* index of insert/delete */
3922     Tcl_DString *dsPtr;        /* Dynamic string in which to append
3923				 * new command. */
3924     int cmdType;		/* type of command to make %-subs for */
3925{
3926    int length, spaceNeeded, cvtFlags;
3927#ifdef TCL_UTF_MAX
3928    Tcl_UniChar ch;
3929#else
3930    char ch;
3931#endif
3932    char *string, buf[INDEX_BUFSIZE];
3933
3934    /* This returns the static value of the string as set in the array */
3935    if (old == NULL && cmdType == CMD_VALIDATE) {
3936	old = TableGetCellValue(tablePtr, r, c);
3937    }
3938
3939    while (1) {
3940	if (*before == '\0') {
3941	    break;
3942	}
3943	/*
3944	 * Find everything up to the next % character and append it
3945	 * to the result string.
3946	 */
3947
3948	string = before;
3949#ifdef TCL_UTF_MAX
3950	/* No need to convert '%', as it is in ascii range */
3951	string = (char *) Tcl_UtfFindFirst(before, '%');
3952#else
3953	string = strchr(before, '%');
3954#endif
3955	if (string == (char *) NULL) {
3956	    Tcl_DStringAppend(dsPtr, before, -1);
3957	    break;
3958	} else if (string != before) {
3959	    Tcl_DStringAppend(dsPtr, before, string-before);
3960	    before = string;
3961	}
3962
3963	/*
3964	 * There's a percent sequence here.  Process it.
3965	 */
3966
3967	before++; /* skip over % */
3968	if (*before != '\0') {
3969#ifdef TCL_UTF_MAX
3970	    before += Tcl_UtfToUniChar(before, &ch);
3971#else
3972	    ch = before[0];
3973	    before++;
3974#endif
3975	} else {
3976	    ch = '%';
3977	}
3978	switch (ch) {
3979	case 'c':
3980	    sprintf(buf, "%d", c);
3981	    string = buf;
3982	    break;
3983	case 'C': /* index of cell */
3984	    TableMakeArrayIndex(r, c, buf);
3985	    string = buf;
3986	    break;
3987	case 'r':
3988	    sprintf(buf, "%d", r);
3989	    string = buf;
3990	    break;
3991	case 'i': /* index of cursor OR |number| of cells selected */
3992	    sprintf(buf, "%d", index);
3993	    string = buf;
3994	    break;
3995	case 's': /* Current cell value */
3996	    string = old;
3997	    break;
3998	case 'S': /* Potential new value of cell */
3999	    string = (new?new:old);
4000	    break;
4001	case 'W': /* widget name */
4002	    string = Tk_PathName(tablePtr->tkwin);
4003	    break;
4004	default:
4005#ifdef TCL_UTF_MAX
4006	    length = Tcl_UniCharToUtf(ch, buf);
4007#else
4008	    buf[0] = ch;
4009	    length = 1;
4010#endif
4011	    buf[length] = '\0';
4012	    string = buf;
4013	    break;
4014	}
4015
4016	spaceNeeded = Tcl_ScanElement(string, &cvtFlags);
4017	length = Tcl_DStringLength(dsPtr);
4018	Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
4019	spaceNeeded = Tcl_ConvertElement(string,
4020					 Tcl_DStringValue(dsPtr) + length,
4021					 cvtFlags | TCL_DONT_USE_BRACES);
4022	Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
4023    }
4024    Tcl_DStringAppend(dsPtr, "", 1);
4025}
4026
4027/* Function to call on loading the Table module */
4028
4029#ifdef BUILD_Tktable
4030#   undef TCL_STORAGE_CLASS
4031#   define TCL_STORAGE_CLASS DLLEXPORT
4032#endif
4033#ifdef MAC_TCL
4034#pragma export on
4035#endif
4036EXTERN int
4037Tktable_Init(interp)
4038     Tcl_Interp *interp;
4039{
4040    /* This defines the static chars tkTable(Safe)InitScript */
4041#include "tkTableInitScript.h"
4042
4043    if (
4044#ifdef USE_TCL_STUBS
4045	Tcl_InitStubs(interp, "8.0", 0)
4046#else
4047	Tcl_PkgRequire(interp, "Tcl", "8.0", 0)
4048#endif
4049	== NULL) {
4050	return TCL_ERROR;
4051    }
4052    if (
4053#ifdef USE_TK_STUBS
4054	Tk_InitStubs(interp, "8.0", 0)
4055#else
4056#    if (TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION == 0)
4057	/* We require 8.0 exact because of the Unicode in 8.1+ */
4058	Tcl_PkgRequire(interp, "Tk", "8.0", 1)
4059#    else
4060	Tcl_PkgRequire(interp, "Tk", "8.0", 0)
4061#    endif
4062#endif
4063	== NULL) {
4064	return TCL_ERROR;
4065    }
4066    if (Tcl_PkgProvide(interp, "Tktable", PACKAGE_VERSION) != TCL_OK) {
4067	return TCL_ERROR;
4068    }
4069    Tcl_CreateObjCommand(interp, TBL_COMMAND, Tk_TableObjCmd,
4070			 (ClientData) Tk_MainWindow(interp),
4071			 (Tcl_CmdDeleteProc *) NULL);
4072
4073    /*
4074     * The init script can't make certain calls in a safe interpreter,
4075     * so we always have to use the embedded runtime for it
4076     */
4077    return Tcl_Eval(interp, Tcl_IsSafe(interp) ?
4078	    tkTableSafeInitScript : tkTableInitScript);
4079}
4080
4081EXTERN int
4082Tktable_SafeInit(interp)
4083     Tcl_Interp *interp;
4084{
4085    return Tktable_Init(interp);
4086}
4087#ifdef MAC_TCL
4088#pragma export reset
4089#endif
4090
4091#ifdef WIN32
4092/*
4093 *----------------------------------------------------------------------
4094 *
4095 * DllEntryPoint --
4096 *
4097 *	This wrapper function is used by Windows to invoke the
4098 *	initialization code for the DLL.  If we are compiling
4099 *	with Visual C++, this routine will be renamed to DllMain.
4100 *	routine.
4101 *
4102 * Results:
4103 *	Returns TRUE;
4104 *
4105 * Side effects:
4106 *	None.
4107 *
4108 *----------------------------------------------------------------------
4109 */
4110
4111BOOL APIENTRY
4112DllEntryPoint(hInst, reason, reserved)
4113     HINSTANCE hInst;		/* Library instance handle. */
4114     DWORD reason;		/* Reason this function is being called. */
4115     LPVOID reserved;		/* Not used. */
4116{
4117    return TRUE;
4118}
4119#endif
4120