1/*
2 * tkTableCmds.c --
3 *
4 *	This module implements general commands of a table widget,
5 *	based on the major/minor command structure.
6 *
7 * Copyright (c) 1998-2002 Jeffrey Hobbs
8 *
9 * See the file "license.txt" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 */
13
14#include "tkTable.h"
15
16/*
17 *--------------------------------------------------------------
18 *
19 * Table_ActivateCmd --
20 *	This procedure is invoked to process the activate method
21 *	that corresponds to a table widget managed by this module.
22 *	See the user documentation for details on what it does.
23 *
24 * Results:
25 *	A standard Tcl result.
26 *
27 * Side effects:
28 *	See the user documentation.
29 *
30 *--------------------------------------------------------------
31 */
32int
33Table_ActivateCmd(ClientData clientData, register Tcl_Interp *interp,
34	      int objc, Tcl_Obj *CONST objv[])
35{
36    register Table *tablePtr = (Table *) clientData;
37    int result = TCL_OK;
38    int row, col, templen;
39
40    if (objc != 3) {
41	Tcl_WrongNumArgs(interp, 2, objv, "index");
42	return TCL_ERROR;
43    } else if (Tcl_GetStringFromObj(objv[2], &templen), templen == 0) {
44	/*
45	 * Test implementation to clear active cell (becroft)
46	 */
47	tablePtr->flags &= ~HAS_ACTIVE;
48	tablePtr->flags |= ACTIVE_DISABLED;
49	tablePtr->activeRow = -1;
50	tablePtr->activeCol = -1;
51	TableAdjustActive(tablePtr);
52	TableConfigCursor(tablePtr);
53    } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) != TCL_OK) {
54	return TCL_ERROR;
55    } else {
56	int x, y, w, dummy;
57	char buf1[INDEX_BUFSIZE], buf2[INDEX_BUFSIZE];
58
59	/* convert to valid active index in real coords */
60	row -= tablePtr->rowOffset;
61	col -= tablePtr->colOffset;
62	/* we do this regardless, to avoid cell commit problems */
63	if ((tablePtr->flags & HAS_ACTIVE) &&
64	    (tablePtr->flags & TEXT_CHANGED)) {
65	    tablePtr->flags &= ~TEXT_CHANGED;
66	    TableSetCellValue(tablePtr,
67			      tablePtr->activeRow+tablePtr->rowOffset,
68			      tablePtr->activeCol+tablePtr->colOffset,
69			      tablePtr->activeBuf);
70	}
71	if (row != tablePtr->activeRow || col != tablePtr->activeCol) {
72	    if (tablePtr->flags & HAS_ACTIVE) {
73		TableMakeArrayIndex(tablePtr->activeRow+tablePtr->rowOffset,
74				    tablePtr->activeCol+tablePtr->colOffset,
75				    buf1);
76	    } else {
77		buf1[0] = '\0';
78	    }
79	    tablePtr->flags |= HAS_ACTIVE;
80	    tablePtr->flags &= ~ACTIVE_DISABLED;
81	    tablePtr->activeRow = row;
82	    tablePtr->activeCol = col;
83	    if (tablePtr->activeTagPtr != NULL) {
84		ckfree((char *) (tablePtr->activeTagPtr));
85		tablePtr->activeTagPtr = NULL;
86	    }
87	    TableAdjustActive(tablePtr);
88	    TableConfigCursor(tablePtr);
89	    if (!(tablePtr->flags & BROWSE_CMD) &&
90		tablePtr->browseCmd != NULL) {
91		Tcl_DString script;
92		tablePtr->flags |= BROWSE_CMD;
93		row = tablePtr->activeRow+tablePtr->rowOffset;
94		col = tablePtr->activeCol+tablePtr->colOffset;
95		TableMakeArrayIndex(row, col, buf2);
96		Tcl_DStringInit(&script);
97		ExpandPercents(tablePtr, tablePtr->browseCmd, row, col,
98			       buf1, buf2, tablePtr->icursor, &script, 0);
99		result = Tcl_GlobalEval(interp, Tcl_DStringValue(&script));
100		if (result == TCL_OK || result == TCL_RETURN) {
101		    Tcl_ResetResult(interp);
102		}
103		Tcl_DStringFree(&script);
104		tablePtr->flags &= ~BROWSE_CMD;
105	    }
106	} else {
107	    char *p = Tcl_GetString(objv[2]);
108
109	    if ((tablePtr->activeTagPtr != NULL) && *p == '@' &&
110		!(tablePtr->flags & ACTIVE_DISABLED) &&
111		TableCellVCoords(tablePtr, row, col, &x, &y, &w, &dummy, 0)) {
112		/* we are clicking into the same cell
113		 * If it was activated with @x,y indexing,
114		 * find the closest char */
115		Tk_TextLayout textLayout;
116		TableTag *tagPtr = tablePtr->activeTagPtr;
117
118		/* no error checking because GetIndex did it for us */
119		p++;
120		x = strtol(p, &p, 0) - x - tablePtr->activeX;
121		p++;
122		y = strtol(p, &p, 0) - y - tablePtr->activeY;
123
124		textLayout = Tk_ComputeTextLayout(tagPtr->tkfont,
125					tablePtr->activeBuf, -1,
126					(tagPtr->wrap) ? w : 0,
127					tagPtr->justify, 0, &dummy, &dummy);
128
129		tablePtr->icursor = Tk_PointToChar(textLayout, x, y);
130		Tk_FreeTextLayout(textLayout);
131		TableRefresh(tablePtr, row, col, CELL|INV_FORCE);
132	    }
133	}
134	tablePtr->flags |= HAS_ACTIVE;
135    }
136    return result;
137}
138
139/*
140 *--------------------------------------------------------------
141 *
142 * Table_AdjustCmd --
143 *	This procedure is invoked to process the width/height method
144 *	that corresponds to a table widget managed by this module.
145 *	See the user documentation for details on what it does.
146 *
147 * Results:
148 *	A standard Tcl result.
149 *
150 * Side effects:
151 *	See the user documentation.
152 *
153 *--------------------------------------------------------------
154 */
155int
156Table_AdjustCmd(ClientData clientData, register Tcl_Interp *interp,
157		int objc, Tcl_Obj *CONST objv[])
158{
159    register Table *tablePtr = (Table *) clientData;
160    Tcl_HashEntry *entryPtr;
161    Tcl_HashSearch search;
162    Tcl_HashTable *hashTablePtr;
163    int i, widthType, dummy, value, posn, offset;
164    char buf1[INDEX_BUFSIZE];
165
166    widthType = (*(Tcl_GetString(objv[1])) == 'w');
167    /* changes the width/height of certain selected columns */
168    if (objc != 3 && (objc & 1)) {
169	Tcl_WrongNumArgs(interp, 2, objv, widthType ?
170			 "?col? ?width col width ...?" :
171			 "?row? ?height row height ...?");
172	return TCL_ERROR;
173    }
174    if (widthType) {
175	hashTablePtr = tablePtr->colWidths;
176	offset = tablePtr->colOffset;
177    } else {
178	hashTablePtr = tablePtr->rowHeights;
179	offset = tablePtr->rowOffset;
180    }
181
182    if (objc == 2) {
183	/* print out all the preset column widths or row heights */
184	entryPtr = Tcl_FirstHashEntry(hashTablePtr, &search);
185	while (entryPtr != NULL) {
186	    posn = ((int) Tcl_GetHashKey(hashTablePtr, entryPtr)) + offset;
187	    value = (int) Tcl_GetHashValue(entryPtr);
188	    sprintf(buf1, "%d %d", posn, value);
189	    /* OBJECTIFY */
190	    Tcl_AppendElement(interp, buf1);
191	    entryPtr = Tcl_NextHashEntry(&search);
192	}
193    } else if (objc == 3) {
194	/* get the width/height of a particular row/col */
195	if (Tcl_GetIntFromObj(interp, objv[2], &posn) != TCL_OK) {
196	    return TCL_ERROR;
197	}
198	/* no range check is done, why bother? */
199	posn -= offset;
200	entryPtr = Tcl_FindHashEntry(hashTablePtr, (char *) posn);
201	if (entryPtr != NULL) {
202	    Tcl_SetIntObj(Tcl_GetObjResult(interp),
203			  (int) Tcl_GetHashValue(entryPtr));
204	} else {
205	    Tcl_SetIntObj(Tcl_GetObjResult(interp), widthType ?
206			  tablePtr->defColWidth : tablePtr->defRowHeight);
207	}
208    } else {
209	for (i=2; i<objc; i++) {
210	    /* set new width|height here */
211	    value = -999999;
212	    if (Tcl_GetIntFromObj(interp, objv[i++], &posn) != TCL_OK ||
213		(strcmp(Tcl_GetString(objv[i]), "default") &&
214		 Tcl_GetIntFromObj(interp, objv[i], &value) != TCL_OK)) {
215		return TCL_ERROR;
216	    }
217	    posn -= offset;
218	    if (value == -999999) {
219		/* reset that field */
220		entryPtr = Tcl_FindHashEntry(hashTablePtr, (char *) posn);
221		if (entryPtr != NULL) {
222		    Tcl_DeleteHashEntry(entryPtr);
223		}
224	    } else {
225		entryPtr = Tcl_CreateHashEntry(hashTablePtr,
226					       (char *) posn, &dummy);
227		Tcl_SetHashValue(entryPtr, (ClientData) value);
228	    }
229	}
230	TableAdjustParams(tablePtr);
231	/* rerequest geometry */
232	TableGeometryRequest(tablePtr);
233	/*
234	 * Invalidate the whole window as TableAdjustParams
235	 * will only check to see if the top left cell has moved
236	 * FIX: should just move from lowest order visible cell
237	 * to edge of window
238	 */
239	TableInvalidateAll(tablePtr, 0);
240    }
241    return TCL_OK;
242}
243
244/*
245 *--------------------------------------------------------------
246 *
247 * Table_BboxCmd --
248 *	This procedure is invoked to process the bbox method
249 *	that corresponds to a table widget managed by this module.
250 *	See the user documentation for details on what it does.
251 *
252 * Results:
253 *	A standard Tcl result.
254 *
255 * Side effects:
256 *	See the user documentation.
257 *
258 *--------------------------------------------------------------
259 */
260int
261Table_BboxCmd(ClientData clientData, register Tcl_Interp *interp,
262	      int objc, Tcl_Obj *CONST objv[])
263{
264    register Table *tablePtr = (Table *) clientData;
265    int x, y, w, h, row, col, key;
266    Tcl_Obj *resultPtr;
267
268    /* Returns bounding box of cell(s) */
269    if (objc < 3 || objc > 4) {
270	Tcl_WrongNumArgs(interp, 2, objv, "first ?last?");
271	return TCL_ERROR;
272    } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) == TCL_ERROR ||
273	       (objc == 4 &&
274		TableGetIndexObj(tablePtr, objv[3], &x, &y) == TCL_ERROR)) {
275	return TCL_ERROR;
276    }
277
278    resultPtr = Tcl_GetObjResult(interp);
279    if (objc == 3) {
280	row -= tablePtr->rowOffset; col -= tablePtr->colOffset;
281	if (TableCellVCoords(tablePtr, row, col, &x, &y, &w, &h, 0)) {
282	    Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewIntObj(x));
283	    Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewIntObj(y));
284	    Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewIntObj(w));
285	    Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewIntObj(h));
286	}
287	return TCL_OK;
288    } else {
289	int r1, c1, r2, c2, minX = 99999, minY = 99999, maxX = 0, maxY = 0;
290
291	row -= tablePtr->rowOffset; col -= tablePtr->colOffset;
292	x -= tablePtr->rowOffset; y -= tablePtr->colOffset;
293	r1 = MIN(row,x); r2 = MAX(row,x);
294	c1 = MIN(col,y); c2 = MAX(col,y);
295	key = 0;
296	for (row = r1; row <= r2; row++) {
297	    for (col = c1; col <= c2; col++) {
298		if (TableCellVCoords(tablePtr, row, col, &x, &y, &w, &h, 0)) {
299		    /* Get max bounding box */
300		    if (x < minX) minX = x;
301		    if (y < minY) minY = y;
302		    if (x+w > maxX) maxX = x+w;
303		    if (y+h > maxY) maxY = y+h;
304		    key++;
305		}
306	    }
307	}
308	if (key) {
309	    Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewIntObj(minX));
310	    Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewIntObj(minY));
311	    Tcl_ListObjAppendElement(NULL, resultPtr,
312				     Tcl_NewIntObj(maxX-minX));
313	    Tcl_ListObjAppendElement(NULL, resultPtr,
314				     Tcl_NewIntObj(maxY-minY));
315	}
316    }
317    return TCL_OK;
318}
319
320static CONST84 char *bdCmdNames[] = {
321    "mark", "dragto", (char *)NULL
322};
323enum bdCmd {
324    BD_MARK, BD_DRAGTO
325};
326
327/*
328 *--------------------------------------------------------------
329 *
330 * Table_BorderCmd --
331 *	This procedure is invoked to process the bbox method
332 *	that corresponds to a table widget managed by this module.
333 *	See the user documentation for details on what it does.
334 *
335 * Results:
336 *	A standard Tcl result.
337 *
338 * Side effects:
339 *	See the user documentation.
340 *
341 *--------------------------------------------------------------
342 */
343int
344Table_BorderCmd(ClientData clientData, register Tcl_Interp *interp,
345		int objc, Tcl_Obj *CONST objv[])
346{
347    register Table *tablePtr = (Table *) clientData;
348    Tcl_HashEntry *entryPtr;
349    int x, y, w, h, row, col, key, dummy, value, cmdIndex;
350    char *rc = NULL;
351    Tcl_Obj *objPtr, *resultPtr;
352
353    if (objc < 5 || objc > 6) {
354	Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y ?row|col?");
355	return TCL_ERROR;
356    }
357    if (Tcl_GetIndexFromObj(interp, objv[2], bdCmdNames,
358			    "option", 0, &cmdIndex) != TCL_OK ||
359	Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
360	Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
361	return TCL_ERROR;
362    }
363    if (objc == 6) {
364	rc = Tcl_GetStringFromObj(objv[5], &w);
365	if ((w < 1) || (strncmp(rc, "row", w) && strncmp(rc, "col", w))) {
366	    Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y ?row|col?");
367	    return TCL_ERROR;
368	}
369    }
370
371    resultPtr = Tcl_GetObjResult(interp);
372    switch ((enum bdCmd) cmdIndex) {
373    case BD_MARK:
374	/* Use x && y to determine if we are over a border */
375	value = TableAtBorder(tablePtr, x, y, &row, &col);
376	/* Cache the row && col for use in DRAGTO */
377	tablePtr->scanMarkRow = row;
378	tablePtr->scanMarkCol = col;
379	if (!value) {
380	    return TCL_OK;
381	}
382	TableCellCoords(tablePtr, row, col, &x, &y, &dummy, &dummy);
383	tablePtr->scanMarkX = x;
384	tablePtr->scanMarkY = y;
385	if (objc == 5 || *rc == 'r') {
386	    if (row < 0) {
387		objPtr = Tcl_NewStringObj("", 0);
388	    } else {
389		objPtr = Tcl_NewIntObj(row+tablePtr->rowOffset);
390	    }
391	    Tcl_ListObjAppendElement(NULL, resultPtr, objPtr);
392	}
393	if (objc == 5 || *rc == 'c') {
394	    if (col < 0) {
395		objPtr = Tcl_NewStringObj("", 0);
396	    } else {
397		objPtr = Tcl_NewIntObj(col+tablePtr->colOffset);
398	    }
399	    Tcl_ListObjAppendElement(NULL, resultPtr, objPtr);
400	}
401	return TCL_OK;	/* BORDER MARK */
402
403    case BD_DRAGTO:
404	/* check to see if we want to resize any borders */
405	if (tablePtr->resize == SEL_NONE) { return TCL_OK; }
406	row = tablePtr->scanMarkRow;
407	col = tablePtr->scanMarkCol;
408	TableCellCoords(tablePtr, row, col, &w, &h, &dummy, &dummy);
409	key = 0;
410	if (row >= 0 && (tablePtr->resize & SEL_ROW)) {
411	    /* row border was active, move it */
412	    value = y-h;
413	    if (value < -1) value = -1;
414	    if (value != tablePtr->scanMarkY) {
415		entryPtr = Tcl_CreateHashEntry(tablePtr->rowHeights,
416					       (char *) row, &dummy);
417		/* -value means rowHeight will be interp'd as pixels, not
418                   lines */
419		Tcl_SetHashValue(entryPtr, (ClientData) MIN(0,-value));
420		tablePtr->scanMarkY = value;
421		key++;
422	    }
423	}
424	if (col >= 0 && (tablePtr->resize & SEL_COL)) {
425	    /* col border was active, move it */
426	    value = x-w;
427	    if (value < -1) value = -1;
428	    if (value != tablePtr->scanMarkX) {
429		entryPtr = Tcl_CreateHashEntry(tablePtr->colWidths,
430					       (char *) col, &dummy);
431		/* -value means colWidth will be interp'd as pixels, not
432                   chars */
433		Tcl_SetHashValue(entryPtr, (ClientData) MIN(0,-value));
434		tablePtr->scanMarkX = value;
435		key++;
436	    }
437	}
438	/* Only if something changed do we want to update */
439	if (key) {
440	    TableAdjustParams(tablePtr);
441	    /* Only rerequest geometry if the basis is the #rows &| #cols */
442	    if (tablePtr->maxReqCols || tablePtr->maxReqRows)
443		TableGeometryRequest(tablePtr);
444	    TableInvalidateAll(tablePtr, 0);
445	}
446	return TCL_OK;	/* BORDER DRAGTO */
447    }
448    return TCL_OK;
449}
450
451/* clear subcommands */
452static CONST84 char *clearNames[] = {
453    "all", "cache", "sizes", "tags", (char *)NULL
454};
455enum clearCommand {
456    CLEAR_ALL, CLEAR_CACHE, CLEAR_SIZES, CLEAR_TAGS
457};
458
459/*
460 *--------------------------------------------------------------
461 *
462 * Table_ClearCmd --
463 *	This procedure is invoked to process the clear method
464 *	that corresponds to a table widget managed by this module.
465 *	See the user documentation for details on what it does.
466 *
467 * Results:
468 *	Cached info can be lost.  Returns valid Tcl result.
469 *
470 * Side effects:
471 *	Can cause redraw.
472 *	See the user documentation.
473 *
474 *--------------------------------------------------------------
475 */
476int
477Table_ClearCmd(ClientData clientData, register Tcl_Interp *interp,
478		int objc, Tcl_Obj *CONST objv[])
479{
480    register Table *tablePtr = (Table *) clientData;
481    int cmdIndex, redraw = 0;
482
483    if (objc < 3 || objc > 5) {
484	Tcl_WrongNumArgs(interp, 2, objv, "option ?first? ?last?");
485	return TCL_ERROR;
486    }
487
488    if (Tcl_GetIndexFromObj(interp, objv[2], clearNames,
489			    "clear option", 0, &cmdIndex) != TCL_OK) {
490	return TCL_ERROR;
491    }
492
493    if (objc == 3) {
494	if (cmdIndex == CLEAR_TAGS || cmdIndex == CLEAR_ALL) {
495	    Tcl_DeleteHashTable(tablePtr->rowStyles);
496	    Tcl_DeleteHashTable(tablePtr->colStyles);
497	    Tcl_DeleteHashTable(tablePtr->cellStyles);
498	    Tcl_DeleteHashTable(tablePtr->flashCells);
499	    Tcl_DeleteHashTable(tablePtr->selCells);
500
501	    /* style hash tables */
502	    Tcl_InitHashTable(tablePtr->rowStyles, TCL_ONE_WORD_KEYS);
503	    Tcl_InitHashTable(tablePtr->colStyles, TCL_ONE_WORD_KEYS);
504	    Tcl_InitHashTable(tablePtr->cellStyles, TCL_STRING_KEYS);
505
506	    /* special style hash tables */
507	    Tcl_InitHashTable(tablePtr->flashCells, TCL_STRING_KEYS);
508	    Tcl_InitHashTable(tablePtr->selCells, TCL_STRING_KEYS);
509	}
510
511	if (cmdIndex == CLEAR_SIZES || cmdIndex == CLEAR_ALL) {
512	    Tcl_DeleteHashTable(tablePtr->colWidths);
513	    Tcl_DeleteHashTable(tablePtr->rowHeights);
514
515	    /* style hash tables */
516	    Tcl_InitHashTable(tablePtr->colWidths, TCL_ONE_WORD_KEYS);
517	    Tcl_InitHashTable(tablePtr->rowHeights, TCL_ONE_WORD_KEYS);
518	}
519
520	if (cmdIndex == CLEAR_CACHE || cmdIndex == CLEAR_ALL) {
521	    Table_ClearHashTable(tablePtr->cache);
522	    Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS);
523	    /* If we were caching and we have no other data source,
524	     * invalidate all the cells */
525	    if (tablePtr->dataSource == DATA_CACHE) {
526		TableGetActiveBuf(tablePtr);
527	    }
528	}
529	redraw = 1;
530    } else {
531	int row, col, r1, r2, c1, c2;
532	Tcl_HashEntry *entryPtr;
533	char buf[INDEX_BUFSIZE], *value;
534
535	if (TableGetIndexObj(tablePtr, objv[3], &row, &col) != TCL_OK ||
536	    ((objc == 5) &&
537	     TableGetIndexObj(tablePtr, objv[4], &r2, &c2) != TCL_OK)) {
538	    return TCL_ERROR;
539	}
540	if (objc == 4) {
541	    r1 = r2 = row;
542	    c1 = c2 = col;
543	} else {
544	    r1 = MIN(row,r2); r2 = MAX(row,r2);
545	    c1 = MIN(col,c2); c2 = MAX(col,c2);
546	}
547	for (row = r1; row <= r2; row++) {
548	    /* Note that *Styles entries are user based (no offset)
549	     * while size entries are 0-based (real) */
550	    if ((cmdIndex == CLEAR_TAGS || cmdIndex == CLEAR_ALL) &&
551		(entryPtr = Tcl_FindHashEntry(tablePtr->rowStyles,
552					      (char *) row))) {
553		Tcl_DeleteHashEntry(entryPtr);
554		redraw = 1;
555	    }
556
557	    if ((cmdIndex == CLEAR_SIZES || cmdIndex == CLEAR_ALL) &&
558		(entryPtr = Tcl_FindHashEntry(tablePtr->rowHeights,
559					      (char *) row-tablePtr->rowOffset))) {
560		Tcl_DeleteHashEntry(entryPtr);
561		redraw = 1;
562	    }
563
564	    for (col = c1; col <= c2; col++) {
565		TableMakeArrayIndex(row, col, buf);
566
567		if (cmdIndex == CLEAR_TAGS || cmdIndex == CLEAR_ALL) {
568		    if ((row == r1) &&
569			(entryPtr = Tcl_FindHashEntry(tablePtr->colStyles,
570						      (char *) col))) {
571			Tcl_DeleteHashEntry(entryPtr);
572			redraw = 1;
573		    }
574		    if ((entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles,
575						      buf))) {
576			Tcl_DeleteHashEntry(entryPtr);
577			redraw = 1;
578		    }
579		    if ((entryPtr = Tcl_FindHashEntry(tablePtr->flashCells,
580						      buf))) {
581			Tcl_DeleteHashEntry(entryPtr);
582			redraw = 1;
583		    }
584		    if ((entryPtr = Tcl_FindHashEntry(tablePtr->selCells,
585						      buf))) {
586			Tcl_DeleteHashEntry(entryPtr);
587			redraw = 1;
588		    }
589		}
590
591		if ((cmdIndex == CLEAR_SIZES || cmdIndex == CLEAR_ALL) &&
592		    row == r1 &&
593		    (entryPtr = Tcl_FindHashEntry(tablePtr->colWidths, (char *)
594						  col-tablePtr->colOffset))) {
595		    Tcl_DeleteHashEntry(entryPtr);
596		    redraw = 1;
597		}
598
599		if ((cmdIndex == CLEAR_CACHE || cmdIndex == CLEAR_ALL) &&
600		    (entryPtr = Tcl_FindHashEntry(tablePtr->cache, buf))) {
601		    value = (char *) Tcl_GetHashValue(entryPtr);
602		    if (value) { ckfree(value); }
603		    Tcl_DeleteHashEntry(entryPtr);
604		    /* if the cache is our data source,
605		     * we need to invalidate the cells changed */
606		    if ((tablePtr->dataSource == DATA_CACHE) &&
607			(row-tablePtr->rowOffset == tablePtr->activeRow &&
608			 col-tablePtr->colOffset == tablePtr->activeCol))
609			TableGetActiveBuf(tablePtr);
610		    redraw = 1;
611		}
612	    }
613	}
614    }
615    /* This could be more sensitive about what it updates,
616     * but that can actually be a lot more costly in some cases */
617    if (redraw) {
618	if (cmdIndex == CLEAR_SIZES || cmdIndex == CLEAR_ALL) {
619	    TableAdjustParams(tablePtr);
620	    /* rerequest geometry */
621	    TableGeometryRequest(tablePtr);
622	}
623	TableInvalidateAll(tablePtr, 0);
624    }
625    return TCL_OK;
626}
627
628/*
629 *--------------------------------------------------------------
630 *
631 * Table_CurselectionCmd --
632 *	This procedure is invoked to process the bbox method
633 *	that corresponds to a table widget managed by this module.
634 *	See the user documentation for details on what it does.
635 *
636 * Results:
637 *	A standard Tcl result.
638 *
639 * Side effects:
640 *	See the user documentation.
641 *
642 *--------------------------------------------------------------
643 */
644int
645Table_CurselectionCmd(ClientData clientData, register Tcl_Interp *interp,
646		      int objc, Tcl_Obj *CONST objv[])
647{
648    register Table *tablePtr = (Table *) clientData;
649    Tcl_HashEntry *entryPtr;
650    Tcl_HashSearch search;
651    char *value = NULL;
652    int row, col;
653
654    if (objc > 3) {
655	Tcl_WrongNumArgs(interp, 2, objv, "?value?");
656	return TCL_ERROR;
657    }
658    if (objc == 3) {
659	/* make sure there is a data source to accept a set value */
660	if ((tablePtr->state == STATE_DISABLED) ||
661	    (tablePtr->dataSource == DATA_NONE)) {
662	    return TCL_OK;
663	}
664	value = Tcl_GetString(objv[2]);
665	for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search);
666	     entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
667	    TableParseArrayIndex(&row, &col,
668				 Tcl_GetHashKey(tablePtr->selCells, entryPtr));
669	    TableSetCellValue(tablePtr, row, col, value);
670	    row -= tablePtr->rowOffset;
671	    col -= tablePtr->colOffset;
672	    if (row == tablePtr->activeRow && col == tablePtr->activeCol) {
673		TableGetActiveBuf(tablePtr);
674	    }
675	    TableRefresh(tablePtr, row, col, CELL);
676	}
677    } else {
678	Tcl_Obj *objPtr = Tcl_NewObj();
679
680	for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search);
681	     entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
682	    value = Tcl_GetHashKey(tablePtr->selCells, entryPtr);
683	    Tcl_ListObjAppendElement(NULL, objPtr,
684				     Tcl_NewStringObj(value, -1));
685	}
686	Tcl_SetObjResult(interp, TableCellSortObj(interp, objPtr));
687    }
688    return TCL_OK;
689}
690
691/*
692 *--------------------------------------------------------------
693 *
694 * Table_CurvalueCmd --
695 *	This procedure is invoked to process the curvalue method
696 *	that corresponds to a table widget managed by this module.
697 *	See the user documentation for details on what it does.
698 *
699 * Results:
700 *	A standard Tcl result.
701 *
702 * Side effects:
703 *	See the user documentation.
704 *
705 *--------------------------------------------------------------
706 */
707int
708Table_CurvalueCmd(ClientData clientData, register Tcl_Interp *interp,
709		  int objc, Tcl_Obj *CONST objv[])
710{
711    register Table *tablePtr = (Table *) clientData;
712
713    if (objc > 3) {
714	Tcl_WrongNumArgs(interp, 2, objv, "?<value>?");
715	return TCL_ERROR;
716    } else if (!(tablePtr->flags & HAS_ACTIVE)) {
717	return TCL_OK;
718    }
719
720    if (objc == 3) {
721	char *value;
722	int len;
723
724	value = Tcl_GetStringFromObj(objv[2], &len);
725	if (STREQ(value, tablePtr->activeBuf)) {
726	    Tcl_SetObjResult(interp, objv[2]);
727	    return TCL_OK;
728	}
729	/* validate potential new active buffer contents
730	 * only accept if validation returns acceptance. */
731	if (tablePtr->validate &&
732	    TableValidateChange(tablePtr,
733				tablePtr->activeRow+tablePtr->rowOffset,
734				tablePtr->activeCol+tablePtr->colOffset,
735				tablePtr->activeBuf,
736				value, tablePtr->icursor) != TCL_OK) {
737	    return TCL_OK;
738	}
739	tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, len+1);
740	strcpy(tablePtr->activeBuf, value);
741	/* mark the text as changed */
742	tablePtr->flags |= TEXT_CHANGED;
743	TableSetActiveIndex(tablePtr);
744	/* check for possible adjustment of icursor */
745	TableGetIcursor(tablePtr, "insert", (int *)0);
746	TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL);
747    }
748
749    Tcl_SetObjResult(interp, Tcl_NewStringObj(tablePtr->activeBuf, -1));
750    return TCL_OK;
751}
752
753/*
754 *--------------------------------------------------------------
755 *
756 * Table_GetCmd --
757 *	This procedure is invoked to process the bbox method
758 *	that corresponds to a table widget managed by this module.
759 *	See the user documentation for details on what it does.
760 *
761 * Results:
762 *	A standard Tcl result.
763 *
764 * Side effects:
765 *	See the user documentation.
766 *
767 *--------------------------------------------------------------
768 */
769int
770Table_GetCmd(ClientData clientData, register Tcl_Interp *interp,
771	     int objc, Tcl_Obj *CONST objv[])
772{
773    register Table *tablePtr = (Table *) clientData;
774    int result = TCL_OK;
775    int r1, c1, r2, c2, row, col;
776
777    if (objc < 3 || objc > 4) {
778	Tcl_WrongNumArgs(interp, 2, objv, "first ?last?");
779	result = TCL_ERROR;
780    } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) == TCL_ERROR) {
781	result = TCL_ERROR;
782    } else if (objc == 3) {
783	Tcl_SetObjResult(interp,
784		Tcl_NewStringObj(TableGetCellValue(tablePtr, row, col), -1));
785    } else if (TableGetIndexObj(tablePtr, objv[3], &r2, &c2) == TCL_ERROR) {
786	result = TCL_ERROR;
787    } else {
788	Tcl_Obj *objPtr = Tcl_NewObj();
789
790	r1 = MIN(row,r2); r2 = MAX(row,r2);
791	c1 = MIN(col,c2); c2 = MAX(col,c2);
792	for ( row = r1; row <= r2; row++ ) {
793	    for ( col = c1; col <= c2; col++ ) {
794		Tcl_ListObjAppendElement(NULL, objPtr,
795			Tcl_NewStringObj(TableGetCellValue(tablePtr,
796				row, col), -1));
797	    }
798	}
799	Tcl_SetObjResult(interp, objPtr);
800    }
801    return result;
802}
803
804/*
805 *--------------------------------------------------------------
806 *
807 * Table_ScanCmd --
808 *	This procedure is invoked to process the scan method
809 *	that corresponds to a table widget managed by this module.
810 *	See the user documentation for details on what it does.
811 *
812 * Results:
813 *	A standard Tcl result.
814 *
815 * Side effects:
816 *	See the user documentation.
817 *
818 *--------------------------------------------------------------
819 */
820int
821Table_ScanCmd(ClientData clientData, register Tcl_Interp *interp,
822	      int objc, Tcl_Obj *CONST objv[])
823{
824    register Table *tablePtr = (Table *) clientData;
825    int x, y, row, col, cmdIndex;
826
827    if (objc != 5) {
828	Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y");
829	return TCL_ERROR;
830    } else if (Tcl_GetIndexFromObj(interp, objv[2], bdCmdNames,
831	    "option", 0, &cmdIndex) != TCL_OK ||
832	    Tcl_GetIntFromObj(interp, objv[3], &x) == TCL_ERROR ||
833	    Tcl_GetIntFromObj(interp, objv[4], &y) == TCL_ERROR) {
834	return TCL_ERROR;
835    }
836    switch ((enum bdCmd) cmdIndex) {
837	case BD_MARK:
838	    TableWhatCell(tablePtr, x, y, &row, &col);
839	    tablePtr->scanMarkRow = row-tablePtr->topRow;
840	    tablePtr->scanMarkCol = col-tablePtr->leftCol;
841	    tablePtr->scanMarkX = x;
842	    tablePtr->scanMarkY = y;
843	    break;
844
845	case BD_DRAGTO: {
846	    int oldTop = tablePtr->topRow, oldLeft = tablePtr->leftCol;
847	    y += (5*(y-tablePtr->scanMarkY));
848	    x += (5*(x-tablePtr->scanMarkX));
849
850	    TableWhatCell(tablePtr, x, y, &row, &col);
851
852	    /* maintain appropriate real index */
853	    tablePtr->topRow  = BETWEEN(row-tablePtr->scanMarkRow,
854		    tablePtr->titleRows, tablePtr->rows-1);
855	    tablePtr->leftCol = BETWEEN(col-tablePtr->scanMarkCol,
856		    tablePtr->titleCols, tablePtr->cols-1);
857
858	    /* Adjust the table if new top left */
859	    if (oldTop != tablePtr->topRow || oldLeft != tablePtr->leftCol) {
860		TableAdjustParams(tablePtr);
861	    }
862	    break;
863	}
864    }
865    return TCL_OK;
866}
867
868/*
869 *--------------------------------------------------------------
870 *
871 * Table_SelAnchorCmd --
872 *	This procedure is invoked to process the selection anchor method
873 *	that corresponds to a table widget managed by this module.
874 *	See the user documentation for details on what it does.
875 *
876 * Results:
877 *	A standard Tcl result.
878 *
879 * Side effects:
880 *	See the user documentation.
881 *
882 *--------------------------------------------------------------
883 */
884int
885Table_SelAnchorCmd(ClientData clientData, register Tcl_Interp *interp,
886		   int objc, Tcl_Obj *CONST objv[])
887{
888    register Table *tablePtr = (Table *) clientData;
889    int row, col;
890
891    if (objc != 4) {
892	Tcl_WrongNumArgs(interp, 3, objv, "index");
893	return TCL_ERROR;
894    } else if (TableGetIndexObj(tablePtr, objv[3], &row, &col) != TCL_OK) {
895	return TCL_ERROR;
896    }
897    tablePtr->flags |= HAS_ANCHOR;
898    /* maintain appropriate real index */
899    if (tablePtr->selectTitles) {
900	tablePtr->anchorRow = BETWEEN(row-tablePtr->rowOffset,
901		0, tablePtr->rows-1);
902	tablePtr->anchorCol = BETWEEN(col-tablePtr->colOffset,
903		0, tablePtr->cols-1);
904    } else {
905	tablePtr->anchorRow = BETWEEN(row-tablePtr->rowOffset,
906		tablePtr->titleRows, tablePtr->rows-1);
907	tablePtr->anchorCol = BETWEEN(col-tablePtr->colOffset,
908		tablePtr->titleCols, tablePtr->cols-1);
909    }
910    return TCL_OK;
911}
912
913/*
914 *--------------------------------------------------------------
915 *
916 * Table_SelClearCmd --
917 *	This procedure is invoked to process the selection clear method
918 *	that corresponds to a table widget managed by this module.
919 *	See the user documentation for details on what it does.
920 *
921 * Results:
922 *	A standard Tcl result.
923 *
924 * Side effects:
925 *	See the user documentation.
926 *
927 *--------------------------------------------------------------
928 */
929int
930Table_SelClearCmd(ClientData clientData, register Tcl_Interp *interp,
931		  int objc, Tcl_Obj *CONST objv[])
932{
933    register Table *tablePtr = (Table *) clientData;
934    int result = TCL_OK;
935    char buf1[INDEX_BUFSIZE];
936    int row, col, key, clo=0,chi=0,r1,c1,r2,c2;
937    Tcl_HashEntry *entryPtr;
938
939    if (objc < 4 || objc > 5) {
940	Tcl_WrongNumArgs(interp, 3, objv, "all|<first> ?<last>?");
941	return TCL_ERROR;
942    }
943    if (STREQ(Tcl_GetString(objv[3]), "all")) {
944	Tcl_HashSearch search;
945	for(entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search);
946	    entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
947	    TableParseArrayIndex(&row, &col,
948				 Tcl_GetHashKey(tablePtr->selCells,entryPtr));
949	    Tcl_DeleteHashEntry(entryPtr);
950	    TableRefresh(tablePtr, row-tablePtr->rowOffset,
951			 col-tablePtr->colOffset, CELL);
952	}
953	return TCL_OK;
954    }
955    if (TableGetIndexObj(tablePtr, objv[3], &row, &col) == TCL_ERROR ||
956	(objc==5 &&
957	 TableGetIndexObj(tablePtr, objv[4], &r2, &c2) == TCL_ERROR)) {
958	return TCL_ERROR;
959    }
960    key = 0;
961    if (objc == 4) {
962	r1 = r2 = row;
963	c1 = c2 = col;
964    } else {
965	r1 = MIN(row,r2); r2 = MAX(row,r2);
966	c1 = MIN(col,c2); c2 = MAX(col,c2);
967    }
968    switch (tablePtr->selectType) {
969    case SEL_BOTH:
970	clo = c1; chi = c2;
971	c1 = tablePtr->colOffset;
972	c2 = tablePtr->cols-1+c1;
973	key = 1;
974	goto CLEAR_CELLS;
975    CLEAR_BOTH:
976	key = 0;
977	c1 = clo; c2 = chi;
978    case SEL_COL:
979	r1 = tablePtr->rowOffset;
980	r2 = tablePtr->rows-1+r1;
981	break;
982    case SEL_ROW:
983	c1 = tablePtr->colOffset;
984	c2 = tablePtr->cols-1+c1;
985	break;
986    }
987    /* row/col are in user index coords */
988CLEAR_CELLS:
989    for ( row = r1; row <= r2; row++ ) {
990	for ( col = c1; col <= c2; col++ ) {
991	    TableMakeArrayIndex(row, col, buf1);
992	    entryPtr = Tcl_FindHashEntry(tablePtr->selCells, buf1);
993	    if (entryPtr != NULL) {
994		Tcl_DeleteHashEntry(entryPtr);
995		TableRefresh(tablePtr, row-tablePtr->rowOffset,
996			     col-tablePtr->colOffset, CELL);
997	    }
998	}
999    }
1000    if (key) goto CLEAR_BOTH;
1001    return result;
1002}
1003
1004/*
1005 *--------------------------------------------------------------
1006 *
1007 * Table_SelIncludesCmd --
1008 *	This procedure is invoked to process the selection includes method
1009 *	that corresponds to a table widget managed by this module.
1010 *	See the user documentation for details on what it does.
1011 *
1012 * Results:
1013 *	A standard Tcl result.
1014 *
1015 * Side effects:
1016 *	See the user documentation.
1017 *
1018 *--------------------------------------------------------------
1019 */
1020int
1021Table_SelIncludesCmd(ClientData clientData, register Tcl_Interp *interp,
1022		     int objc, Tcl_Obj *CONST objv[])
1023{
1024    register Table *tablePtr = (Table *) clientData;
1025    int row, col;
1026
1027    if (objc != 4) {
1028	Tcl_WrongNumArgs(interp, 3, objv, "index");
1029	return TCL_ERROR;
1030    } else if (TableGetIndexObj(tablePtr, objv[3], &row, &col) == TCL_ERROR) {
1031	return TCL_ERROR;
1032    } else {
1033	char buf[INDEX_BUFSIZE];
1034	TableMakeArrayIndex(row, col, buf);
1035	Tcl_SetBooleanObj(Tcl_GetObjResult(interp),
1036			  (Tcl_FindHashEntry(tablePtr->selCells, buf)!=NULL));
1037    }
1038    return TCL_OK;
1039}
1040
1041/*
1042 *--------------------------------------------------------------
1043 *
1044 * Table_SelSetCmd --
1045 *	This procedure is invoked to process the selection set method
1046 *	that corresponds to a table widget managed by this module.
1047 *	See the user documentation for details on what it does.
1048 *
1049 * Results:
1050 *	A standard Tcl result.
1051 *
1052 * Side effects:
1053 *	See the user documentation.
1054 *
1055 *--------------------------------------------------------------
1056 */
1057int
1058Table_SelSetCmd(ClientData clientData, register Tcl_Interp *interp,
1059		int objc, Tcl_Obj *CONST objv[])
1060{
1061    register Table *tablePtr = (Table *) clientData;
1062    int row, col, dummy, key;
1063    char buf1[INDEX_BUFSIZE];
1064    Tcl_HashSearch search;
1065    Tcl_HashEntry *entryPtr;
1066
1067    int clo=0, chi=0, r1, c1, r2, c2, firstRow, firstCol, lastRow, lastCol;
1068    if (objc < 4 || objc > 5) {
1069	Tcl_WrongNumArgs(interp, 3, objv, "first ?last?");
1070	return TCL_ERROR;
1071    }
1072    if (TableGetIndexObj(tablePtr, objv[3], &row, &col) == TCL_ERROR ||
1073	(objc==5 &&
1074	 TableGetIndexObj(tablePtr, objv[4], &r2, &c2) == TCL_ERROR)) {
1075	return TCL_ERROR;
1076    }
1077    key = 0;
1078    lastRow = tablePtr->rows-1+tablePtr->rowOffset;
1079    lastCol = tablePtr->cols-1+tablePtr->colOffset;
1080    if (tablePtr->selectTitles) {
1081	firstRow = tablePtr->rowOffset;
1082	firstCol = tablePtr->colOffset;
1083    } else {
1084	firstRow = tablePtr->titleRows+tablePtr->rowOffset;
1085	firstCol = tablePtr->titleCols+tablePtr->colOffset;
1086    }
1087    /* maintain appropriate user index */
1088    CONSTRAIN(row, firstRow, lastRow);
1089    CONSTRAIN(col, firstCol, lastCol);
1090    if (objc == 4) {
1091	r1 = r2 = row;
1092	c1 = c2 = col;
1093    } else {
1094	CONSTRAIN(r2, firstRow, lastRow);
1095	CONSTRAIN(c2, firstCol, lastCol);
1096	r1 = MIN(row,r2); r2 = MAX(row,r2);
1097	c1 = MIN(col,c2); c2 = MAX(col,c2);
1098    }
1099    switch (tablePtr->selectType) {
1100    case SEL_BOTH:
1101	if (firstCol > lastCol) c2--; /* No selectable columns in table */
1102	if (firstRow > lastRow) r2--; /* No selectable rows in table */
1103	clo = c1; chi = c2;
1104	c1 = firstCol;
1105	c2 = lastCol;
1106	key = 1;
1107	goto SET_CELLS;
1108    SET_BOTH:
1109	key = 0;
1110	c1 = clo; c2 = chi;
1111    case SEL_COL:
1112	r1 = firstRow;
1113	r2 = lastRow;
1114	if (firstCol > lastCol) c2--; /* No selectable columns in table */
1115	break;
1116    case SEL_ROW:
1117	c1 = firstCol;
1118	c2 = lastCol;
1119	if (firstRow>lastRow) r2--; /* No selectable rows in table */
1120	break;
1121    }
1122SET_CELLS:
1123    entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search);
1124    for ( row = r1; row <= r2; row++ ) {
1125	for ( col = c1; col <= c2; col++ ) {
1126	    TableMakeArrayIndex(row, col, buf1);
1127	    if (Tcl_FindHashEntry(tablePtr->selCells, buf1) == NULL) {
1128		Tcl_CreateHashEntry(tablePtr->selCells, buf1, &dummy);
1129		TableRefresh(tablePtr, row-tablePtr->rowOffset,
1130			     col-tablePtr->colOffset, CELL);
1131	    }
1132	}
1133    }
1134    if (key) goto SET_BOTH;
1135
1136    /* Adjust the table for top left, selection on screen etc */
1137    TableAdjustParams(tablePtr);
1138
1139    /* If the table was previously empty and we want to export the
1140     * selection, we should grab it now */
1141    if (entryPtr == NULL && tablePtr->exportSelection) {
1142	Tk_OwnSelection(tablePtr->tkwin, XA_PRIMARY, TableLostSelection,
1143			(ClientData) tablePtr);
1144    }
1145    return TCL_OK;
1146}
1147
1148/*
1149 *--------------------------------------------------------------
1150 *
1151 * Table_ViewCmd --
1152 *	This procedure is invoked to process the x|yview method
1153 *	that corresponds to a table widget managed by this module.
1154 *	See the user documentation for details on what it does.
1155 *
1156 * Results:
1157 *	A standard Tcl result.
1158 *
1159 * Side effects:
1160 *	See the user documentation.
1161 *
1162 *--------------------------------------------------------------
1163 */
1164int
1165Table_ViewCmd(ClientData clientData, register Tcl_Interp *interp,
1166	      int objc, Tcl_Obj *CONST objv[])
1167{
1168    register Table *tablePtr = (Table *) clientData;
1169    int row, col, value;
1170    char *xy;
1171
1172    /* Check xview or yview */
1173    if (objc > 5) {
1174	Tcl_WrongNumArgs(interp, 2, objv, "?args?");
1175	return TCL_ERROR;
1176    }
1177    xy = Tcl_GetString(objv[1]);
1178
1179    if (objc == 2) {
1180	Tcl_Obj *resultPtr;
1181	int diff, x, y, w, h;
1182	double first, last;
1183
1184	resultPtr = Tcl_GetObjResult(interp);
1185	TableGetLastCell(tablePtr, &row, &col);
1186	TableCellVCoords(tablePtr, row, col, &x, &y, &w, &h, 0);
1187	if (*xy == 'y') {
1188	    if (row < tablePtr->titleRows) {
1189		first = 0;
1190		last  = 1;
1191	    } else {
1192		diff = tablePtr->rowStarts[tablePtr->titleRows];
1193		last = (double) (tablePtr->rowStarts[tablePtr->rows]-diff);
1194		first = (tablePtr->rowStarts[tablePtr->topRow]-diff) / last;
1195		last  = (h+tablePtr->rowStarts[row]-diff) / last;
1196	    }
1197	} else {
1198	    if (col < tablePtr->titleCols) {
1199		first = 0;
1200		last  = 1;
1201	    } else {
1202		diff = tablePtr->colStarts[tablePtr->titleCols];
1203		last = (double) (tablePtr->colStarts[tablePtr->cols]-diff);
1204		first = (tablePtr->colStarts[tablePtr->leftCol]-diff) / last;
1205		last  = (w+tablePtr->colStarts[col]-diff) / last;
1206	    }
1207	}
1208	Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewDoubleObj(first));
1209	Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewDoubleObj(last));
1210    } else {
1211	/* cache old topleft to see if it changes */
1212	int oldTop = tablePtr->topRow, oldLeft = tablePtr->leftCol;
1213
1214	if (objc == 3) {
1215	    if (Tcl_GetIntFromObj(interp, objv[2], &value) != TCL_OK) {
1216		return TCL_ERROR;
1217	    }
1218	    if (*xy == 'y') {
1219		tablePtr->topRow  = value + tablePtr->titleRows;
1220	    } else {
1221		tablePtr->leftCol = value + tablePtr->titleCols;
1222	    }
1223	} else {
1224	    int result;
1225	    double frac;
1226#if (TK_MINOR_VERSION > 0) /* 8.1+ */
1227	    result = Tk_GetScrollInfoObj(interp, objc, objv, &frac, &value);
1228#else
1229	    int i;
1230	    char **argv = (char **) ckalloc((objc + 1) * sizeof(char *));
1231	    for (i = 0; i < objc; i++) {
1232		argv[i] = Tcl_GetString(objv[i]);
1233	    }
1234	    argv[i] = NULL;
1235	    result = Tk_GetScrollInfo(interp, objc, argv, &frac, &value);
1236	    ckfree ((char *) argv);
1237#endif
1238	    switch (result) {
1239	    case TK_SCROLL_ERROR:
1240		return TCL_ERROR;
1241	    case TK_SCROLL_MOVETO:
1242		if (frac < 0) frac = 0;
1243		if (*xy == 'y') {
1244		    tablePtr->topRow = (int)(frac*tablePtr->rows)
1245			+tablePtr->titleRows;
1246		} else {
1247		    tablePtr->leftCol = (int)(frac*tablePtr->cols)
1248			+tablePtr->titleCols;
1249		}
1250		break;
1251	    case TK_SCROLL_PAGES:
1252		TableGetLastCell(tablePtr, &row, &col);
1253		if (*xy == 'y') {
1254		    tablePtr->topRow  += value * (row-tablePtr->topRow+1);
1255		} else {
1256		    tablePtr->leftCol += value * (col-tablePtr->leftCol+1);
1257		}
1258		break;
1259	    case TK_SCROLL_UNITS:
1260		if (*xy == 'y') {
1261		    tablePtr->topRow  += value;
1262		} else {
1263		    tablePtr->leftCol += value;
1264		}
1265		break;
1266	    }
1267	}
1268	/* maintain appropriate real index */
1269	CONSTRAIN(tablePtr->topRow, tablePtr->titleRows, tablePtr->rows-1);
1270	CONSTRAIN(tablePtr->leftCol, tablePtr->titleCols, tablePtr->cols-1);
1271	/* Do the table adjustment if topRow || leftCol changed */
1272	if (oldTop != tablePtr->topRow || oldLeft != tablePtr->leftCol) {
1273	    TableAdjustParams(tablePtr);
1274	}
1275    }
1276
1277    return TCL_OK;
1278}
1279
1280#if 0
1281/*
1282 *--------------------------------------------------------------
1283 *
1284 * Table_Cmd --
1285 *	This procedure is invoked to process the CMD method
1286 *	that corresponds to a table widget managed by this module.
1287 *	See the user documentation for details on what it does.
1288 *
1289 * Results:
1290 *	A standard Tcl result.
1291 *
1292 * Side effects:
1293 *	See the user documentation.
1294 *
1295 *--------------------------------------------------------------
1296 */
1297int
1298Table_Cmd(ClientData clientData, register Tcl_Interp *interp,
1299	  int objc, Tcl_Obj *CONST objv[])
1300{
1301    register Table *tablePtr = (Table *) clientData;
1302    int result = TCL_OK;
1303
1304    return result;
1305}
1306#endif
1307