1/*
2 * tkTableEdit.c --
3 *
4 *	This module implements editing functions of a table widget.
5 *
6 * Copyright (c) 1998-2000 Jeffrey Hobbs
7 *
8 * See the file "license.terms" for information on usage and redistribution
9 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10 *
11 * RCS: @(#) $Id: tkTableEdit.c,v 1.7 2002/10/16 07:30:56 hobbs Exp $
12 */
13
14#include "tkTable.h"
15
16static void	TableModifyRC _ANSI_ARGS_((register Table *tablePtr,
17			int doRows, int movetag,
18			Tcl_HashTable *tagTblPtr, Tcl_HashTable *dimTblPtr,
19			int offset, int from, int to, int lo, int hi,
20			int outOfBounds));
21
22/* insert/delete subcommands */
23static CONST84 char *modCmdNames[] = {
24    "active", "cols", "rows", (char *)NULL
25};
26enum modCmd {
27    MOD_ACTIVE, MOD_COLS, MOD_ROWS
28};
29
30/* insert/delete row/col switches */
31static CONST84 char *rcCmdNames[] = {
32    "-keeptitles",	"-holddimensions",	"-holdselection",
33    "-holdtags",	"-holdwindows",	"--",
34    (char *) NULL
35};
36enum rcCmd {
37    OPT_TITLES,	OPT_DIMS,	OPT_SEL,
38    OPT_TAGS,	OPT_WINS,	OPT_LAST
39};
40
41#define HOLD_TITLES	1<<0
42#define HOLD_DIMS	1<<1
43#define HOLD_TAGS	1<<2
44#define HOLD_WINS	1<<3
45#define HOLD_SEL	1<<4
46
47
48/*
49 *--------------------------------------------------------------
50 *
51 * Table_EditCmd --
52 *	This procedure is invoked to process the insert/delete method
53 *	that corresponds to a table widget managed by this module.
54 *	See the user documentation for details on what it does.
55 *
56 * Results:
57 *	A standard Tcl result.
58 *
59 * Side effects:
60 *	See the user documentation.
61 *
62 *--------------------------------------------------------------
63 */
64int
65Table_EditCmd(ClientData clientData, register Tcl_Interp *interp,
66	      int objc, Tcl_Obj *CONST objv[])
67{
68    register Table *tablePtr = (Table *) clientData;
69    int doInsert, cmdIndex, first, last;
70
71    if (objc < 4) {
72	Tcl_WrongNumArgs(interp, 2, objv,
73			 "option ?switches? arg ?arg?");
74	return TCL_ERROR;
75    }
76    if (Tcl_GetIndexFromObj(interp, objv[2], modCmdNames,
77			    "option", 0, &cmdIndex) != TCL_OK) {
78	return TCL_ERROR;
79    }
80
81    doInsert = (*(Tcl_GetString(objv[1])) == 'i');
82    switch ((enum modCmd) cmdIndex) {
83    case MOD_ACTIVE:
84	if (doInsert) {
85	    /* INSERT */
86	    if (objc != 5) {
87		Tcl_WrongNumArgs(interp, 3, objv, "index string");
88		return TCL_ERROR;
89	    }
90	    if (TableGetIcursorObj(tablePtr, objv[3], &first) != TCL_OK) {
91		return TCL_ERROR;
92	    } else if ((tablePtr->flags & HAS_ACTIVE) &&
93		       !(tablePtr->flags & ACTIVE_DISABLED) &&
94		       tablePtr->state == STATE_NORMAL) {
95		TableInsertChars(tablePtr, first, Tcl_GetString(objv[4]));
96	    }
97	} else {
98	    /* DELETE */
99	    if (objc > 5) {
100		Tcl_WrongNumArgs(interp, 3, objv, "first ?last?");
101		return TCL_ERROR;
102	    }
103	    if (TableGetIcursorObj(tablePtr, objv[3], &first) != TCL_OK) {
104		return TCL_ERROR;
105	    }
106	    if (objc == 4) {
107		last = first+1;
108	    } else if (TableGetIcursorObj(tablePtr, objv[4],
109					  &last) != TCL_OK) {
110		return TCL_ERROR;
111	    }
112	    if ((last >= first) && (tablePtr->flags & HAS_ACTIVE) &&
113		!(tablePtr->flags & ACTIVE_DISABLED) &&
114		tablePtr->state == STATE_NORMAL) {
115		TableDeleteChars(tablePtr, first, last-first);
116	    }
117	}
118	break;	/* EDIT ACTIVE */
119
120    case MOD_COLS:
121    case MOD_ROWS: {
122	/*
123	 * ROW/COL INSERTION/DELETION
124	 * FIX: This doesn't handle spans
125	 */
126	int i, lo, hi, argsLeft, offset, minkeyoff, doRows;
127	int maxrow, maxcol, maxkey, minkey, flags, count, *dimPtr;
128	Tcl_HashTable *tagTblPtr, *dimTblPtr;
129	Tcl_HashSearch search;
130
131	doRows	= (cmdIndex == MOD_ROWS);
132	flags	= 0;
133	for (i = 3; i < objc; i++) {
134	    if (*(Tcl_GetString(objv[i])) != '-') {
135		break;
136	    }
137	    if (Tcl_GetIndexFromObj(interp, objv[i], rcCmdNames,
138				    "switch", 0, &cmdIndex) != TCL_OK) {
139		return TCL_ERROR;
140	    }
141	    if (cmdIndex == OPT_LAST) {
142		i++;
143		break;
144	    }
145	    switch (cmdIndex) {
146	    case OPT_TITLES:
147		flags |= HOLD_TITLES;
148		break;
149	    case OPT_DIMS:
150		flags |= HOLD_DIMS;
151		break;
152 	    case OPT_SEL:
153 		flags |= HOLD_SEL;
154 		break;
155	    case OPT_TAGS:
156		flags |= HOLD_TAGS;
157		break;
158	    case OPT_WINS:
159		flags |= HOLD_WINS;
160		break;
161	    }
162	}
163	argsLeft = objc - i;
164	if (argsLeft < 1 || argsLeft > 2) {
165	    Tcl_WrongNumArgs(interp, 3, objv, "?switches? index ?count?");
166	    return TCL_ERROR;
167	}
168
169	count	= 1;
170	maxcol	= tablePtr->cols-1+tablePtr->colOffset;
171	maxrow	= tablePtr->rows-1+tablePtr->rowOffset;
172	if (strcmp(Tcl_GetString(objv[i]), "end") == 0) {
173	    /* allow "end" to be specified as an index */
174	    first = (doRows) ? maxrow : maxcol;
175	} else if (Tcl_GetIntFromObj(interp, objv[i], &first) != TCL_OK) {
176	    return TCL_ERROR;
177	}
178	if (argsLeft == 2 &&
179	    Tcl_GetIntFromObj(interp, objv[++i], &count) != TCL_OK) {
180	    return TCL_ERROR;
181	}
182	if (count == 0 || (tablePtr->state == STATE_DISABLED)) {
183	    return TCL_OK;
184	}
185
186	if (doRows) {
187	    maxkey	= maxrow;
188	    minkey	= tablePtr->rowOffset;
189	    minkeyoff	= tablePtr->rowOffset+tablePtr->titleRows;
190	    offset	= tablePtr->rowOffset;
191	    tagTblPtr	= tablePtr->rowStyles;
192	    dimTblPtr	= tablePtr->rowHeights;
193	    dimPtr	= &(tablePtr->rows);
194	    lo		= tablePtr->colOffset
195		+ ((flags & HOLD_TITLES) ? tablePtr->titleCols : 0);
196	    hi		= maxcol;
197	} else {
198	    maxkey	= maxcol;
199	    minkey	= tablePtr->colOffset;
200	    minkeyoff	= tablePtr->colOffset+tablePtr->titleCols;
201	    offset	= tablePtr->colOffset;
202	    tagTblPtr	= tablePtr->colStyles;
203	    dimTblPtr	= tablePtr->colWidths;
204	    dimPtr	= &(tablePtr->cols);
205	    lo		= tablePtr->rowOffset
206		+ ((flags & HOLD_TITLES) ? tablePtr->titleRows : 0);
207	    hi		= maxrow;
208	}
209
210	/* constrain the starting index */
211	if (first > maxkey) {
212	    first = maxkey;
213	} else if (first < minkey) {
214	    first = minkey;
215	}
216	if (doInsert) {
217	    /* +count means insert after index,
218	     * -count means insert before index */
219	    if (count < 0) {
220		count = -count;
221	    } else {
222		first++;
223	    }
224	    if ((flags & HOLD_TITLES) && (first < minkeyoff)) {
225		count -= minkeyoff-first;
226		if (count <= 0) {
227		    return TCL_OK;
228		}
229		first = minkeyoff;
230	    }
231	    if (!(flags & HOLD_DIMS)) {
232		maxkey += count;
233		*dimPtr += count;
234	    }
235	    /*
236	     * We need to call TableAdjustParams before TableModifyRC to
237	     * ensure that side effect code like var traces that might get
238	     * called will access the correct new dimensions.
239	     */
240	    if (*dimPtr < 1) {
241		*dimPtr = 1;
242	    }
243	    TableAdjustParams(tablePtr);
244	    for (i = maxkey; i >= first; i--) {
245		/* move row/col style && width/height here */
246		TableModifyRC(tablePtr, doRows, flags, tagTblPtr, dimTblPtr,
247			offset, i, i-count, lo, hi, ((i-count) < first));
248	    }
249	    if (!(flags & HOLD_WINS)) {
250		/*
251		 * This may be a little severe, but it does unmap the
252		 * windows that need to be unmapped, and those that should
253		 * stay do remap correctly. [Bug #551325]
254		 */
255		if (doRows) {
256		    EmbWinUnmap(tablePtr,
257			    first - tablePtr->rowOffset,
258			    maxkey - tablePtr->rowOffset,
259			    lo - tablePtr->colOffset,
260			    hi - tablePtr->colOffset);
261		} else {
262		    EmbWinUnmap(tablePtr,
263			    lo - tablePtr->rowOffset,
264			    hi - tablePtr->rowOffset,
265			    first - tablePtr->colOffset,
266			    maxkey - tablePtr->colOffset);
267		}
268	    }
269	} else {
270	    /* (index = i && count = 1) == (index = i && count = -1) */
271	    if (count < 0) {
272		/* if the count is negative, make sure that the col count will
273		 * delete no greater than the original index */
274		if (first+count < minkey) {
275		    if (first-minkey < abs(count)) {
276			/*
277			 * In this case, the user is asking to delete more rows
278			 * than exist before the minkey, so we have to shrink
279			 * the count down to the existing rows up to index.
280			 */
281			count = first-minkey;
282		    } else {
283			count += first-minkey;
284		    }
285		    first = minkey;
286		} else {
287		    first += count;
288		    count = -count;
289		}
290	    }
291	    if ((flags & HOLD_TITLES) && (first <= minkeyoff)) {
292		count -= minkeyoff-first;
293		if (count <= 0) {
294		    return TCL_OK;
295		}
296		first = minkeyoff;
297	    }
298	    if (count > maxkey-first+1) {
299		count = maxkey-first+1;
300	    }
301	    if (!(flags & HOLD_DIMS)) {
302		*dimPtr -= count;
303	    }
304	    /*
305	     * We need to call TableAdjustParams before TableModifyRC to
306	     * ensure that side effect code like var traces that might get
307	     * called will access the correct new dimensions.
308	     */
309	    if (*dimPtr < 1) {
310		*dimPtr = 1;
311	    }
312	    TableAdjustParams(tablePtr);
313	    for (i = first; i <= maxkey; i++) {
314		TableModifyRC(tablePtr, doRows, flags, tagTblPtr, dimTblPtr,
315			offset, i, i+count, lo, hi, ((i+count) > maxkey));
316	    }
317	}
318	if (!(flags & HOLD_SEL) &&
319		Tcl_FirstHashEntry(tablePtr->selCells, &search) != NULL) {
320	    /* clear selection - forceful, but effective */
321	    Tcl_DeleteHashTable(tablePtr->selCells);
322	    Tcl_InitHashTable(tablePtr->selCells, TCL_STRING_KEYS);
323	}
324
325	/*
326	 * Make sure that the modified dimension is actually legal
327	 * after removing all that stuff.
328	 */
329	if (*dimPtr < 1) {
330	    *dimPtr = 1;
331	    TableAdjustParams(tablePtr);
332	}
333
334	/* change the geometry */
335	TableGeometryRequest(tablePtr);
336	/* FIX:
337	 * This has to handle when the previous rows/cols resize because
338	 * of the *stretchmode.  InvalidateAll does that, but could be
339	 * more efficient.
340	 */
341	TableInvalidateAll(tablePtr, 0);
342	break;
343    }
344
345    }
346    return TCL_OK;
347}
348
349/*
350 *----------------------------------------------------------------------
351 *
352 * TableDeleteChars --
353 *	Remove one or more characters from an table widget.
354 *
355 * Results:
356 *	None.
357 *
358 * Side effects:
359 *	Memory gets freed, the table gets modified and (eventually)
360 *	redisplayed.
361 *
362 *----------------------------------------------------------------------
363 */
364void
365TableDeleteChars(tablePtr, index, count)
366    register Table *tablePtr;	/* Table widget to modify. */
367    int index;			/* Index of first character to delete. */
368    int count;			/* How many characters to delete. */
369{
370#ifdef TCL_UTF_MAX
371    int byteIndex, byteCount, newByteCount, numBytes, numChars;
372    char *new, *string;
373
374    string = tablePtr->activeBuf;
375    numBytes = strlen(string);
376    numChars = Tcl_NumUtfChars(string, numBytes);
377    if ((index + count) > numChars) {
378	count = numChars - index;
379    }
380    if (count <= 0) {
381	return;
382    }
383
384    byteIndex = Tcl_UtfAtIndex(string, index) - string;
385    byteCount = Tcl_UtfAtIndex(string + byteIndex, count)
386	- (string + byteIndex);
387
388    newByteCount = numBytes + 1 - byteCount;
389    new = (char *) ckalloc((unsigned) newByteCount);
390    memcpy(new, string, (size_t) byteIndex);
391    strcpy(new + byteIndex, string + byteIndex + byteCount);
392#else
393    int oldlen;
394    char *new;
395
396    /* this gets the length of the string, as well as ensuring that
397     * the cursor isn't beyond the end char */
398    TableGetIcursor(tablePtr, "end", &oldlen);
399
400    if ((index+count) > oldlen)
401	count = oldlen-index;
402    if (count <= 0)
403	return;
404
405    new = (char *) ckalloc((unsigned)(oldlen-count+1));
406    strncpy(new, tablePtr->activeBuf, (size_t) index);
407    strcpy(new+index, tablePtr->activeBuf+index+count);
408    /* make sure this string is null terminated */
409    new[oldlen-count] = '\0';
410#endif
411    /* This prevents deletes on BREAK or validation error. */
412    if (tablePtr->validate &&
413	TableValidateChange(tablePtr, tablePtr->activeRow+tablePtr->rowOffset,
414			    tablePtr->activeCol+tablePtr->colOffset,
415			    tablePtr->activeBuf, new, index) != TCL_OK) {
416	ckfree(new);
417	return;
418    }
419
420    ckfree(tablePtr->activeBuf);
421    tablePtr->activeBuf = new;
422
423    /* mark the text as changed */
424    tablePtr->flags |= TEXT_CHANGED;
425
426    if (tablePtr->icursor >= index) {
427	if (tablePtr->icursor >= (index+count)) {
428	    tablePtr->icursor -= count;
429	} else {
430	    tablePtr->icursor = index;
431	}
432    }
433
434    TableSetActiveIndex(tablePtr);
435
436    TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL);
437}
438
439/*
440 *----------------------------------------------------------------------
441 *
442 * TableInsertChars --
443 *	Add new characters to the active cell of a table widget.
444 *
445 * Results:
446 *	None.
447 *
448 * Side effects:
449 *	New information gets added to tablePtr; it will be redisplayed
450 *	soon, but not necessarily immediately.
451 *
452 *----------------------------------------------------------------------
453 */
454void
455TableInsertChars(tablePtr, index, value)
456    register Table *tablePtr;	/* Table that is to get the new elements. */
457    int index;			/* Add the new elements before this element. */
458    char *value;		/* New characters to add (NULL-terminated
459				 * string). */
460{
461#ifdef TCL_UTF_MAX
462    int oldlen, byteIndex, byteCount;
463    char *new, *string;
464
465    byteCount = strlen(value);
466    if (byteCount == 0) {
467	return;
468    }
469
470    /* Is this an autoclear and this is the first update */
471    /* Note that this clears without validating */
472    if (tablePtr->autoClear && !(tablePtr->flags & TEXT_CHANGED)) {
473	/* set the buffer to be empty */
474	tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, 1);
475	tablePtr->activeBuf[0] = '\0';
476	/* the insert position now has to be 0 */
477	index = 0;
478	tablePtr->icursor = 0;
479    }
480
481    string = tablePtr->activeBuf;
482    byteIndex = Tcl_UtfAtIndex(string, index) - string;
483
484    oldlen = strlen(string);
485    new = (char *) ckalloc((unsigned)(oldlen + byteCount + 1));
486    memcpy(new, string, (size_t) byteIndex);
487    strcpy(new + byteIndex, value);
488    strcpy(new + byteIndex + byteCount, string + byteIndex);
489
490    /* validate potential new active buffer */
491    /* This prevents inserts on either BREAK or validation error. */
492    if (tablePtr->validate &&
493	TableValidateChange(tablePtr, tablePtr->activeRow+tablePtr->rowOffset,
494			    tablePtr->activeCol+tablePtr->colOffset,
495			    tablePtr->activeBuf, new, byteIndex) != TCL_OK) {
496	ckfree(new);
497	return;
498    }
499
500    /*
501     * The following construction is used because inserting improperly
502     * formed UTF-8 sequences between other improperly formed UTF-8
503     * sequences could result in actually forming valid UTF-8 sequences;
504     * the number of characters added may not be Tcl_NumUtfChars(string, -1),
505     * because of context.  The actual number of characters added is how
506     * many characters were are in the string now minus the number that
507     * used to be there.
508     */
509
510    if (tablePtr->icursor >= index) {
511	tablePtr->icursor += Tcl_NumUtfChars(new, oldlen+byteCount)
512	    - Tcl_NumUtfChars(tablePtr->activeBuf, oldlen);
513    }
514
515    ckfree(string);
516    tablePtr->activeBuf = new;
517
518#else
519    int oldlen, newlen;
520    char *new;
521
522    newlen = strlen(value);
523    if (newlen == 0) return;
524
525    /* Is this an autoclear and this is the first update */
526    /* Note that this clears without validating */
527    if (tablePtr->autoClear && !(tablePtr->flags & TEXT_CHANGED)) {
528	/* set the buffer to be empty */
529	tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, 1);
530	tablePtr->activeBuf[0] = '\0';
531	/* the insert position now has to be 0 */
532	index = 0;
533    }
534    oldlen = strlen(tablePtr->activeBuf);
535    /* get the buffer to at least the right length */
536    new = (char *) ckalloc((unsigned)(oldlen+newlen+1));
537    strncpy(new, tablePtr->activeBuf, (size_t) index);
538    strcpy(new+index, value);
539    strcpy(new+index+newlen, (tablePtr->activeBuf)+index);
540    /* make sure this string is null terminated */
541    new[oldlen+newlen] = '\0';
542
543    /* validate potential new active buffer */
544    /* This prevents inserts on either BREAK or validation error. */
545    if (tablePtr->validate &&
546	TableValidateChange(tablePtr, tablePtr->activeRow+tablePtr->rowOffset,
547			    tablePtr->activeCol+tablePtr->colOffset,
548			    tablePtr->activeBuf, new, index) != TCL_OK) {
549	ckfree(new);
550	return;
551    }
552    ckfree(tablePtr->activeBuf);
553    tablePtr->activeBuf = new;
554
555    if (tablePtr->icursor >= index) {
556	tablePtr->icursor += newlen;
557    }
558#endif
559
560    /* mark the text as changed */
561    tablePtr->flags |= TEXT_CHANGED;
562
563    TableSetActiveIndex(tablePtr);
564
565    TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL);
566}
567
568/*
569 *----------------------------------------------------------------------
570 *
571 * TableModifyRC --
572 *	Helper function that does the core work of moving rows/cols
573 *	and associated tags.
574 *
575 * Results:
576 *	None.
577 *
578 * Side effects:
579 *	Moves cell data and possibly tag data
580 *
581 *----------------------------------------------------------------------
582 */
583static void
584TableModifyRC(tablePtr, doRows, flags, tagTblPtr, dimTblPtr,
585	      offset, from, to, lo, hi, outOfBounds)
586    Table *tablePtr;	/* Information about text widget. */
587    int doRows;		/* rows (1) or cols (0) */
588    int flags;		/* flags indicating what to move */
589    Tcl_HashTable *tagTblPtr, *dimTblPtr; /* Pointers to the row/col tags
590					   * and width/height tags */
591    int offset;		/* appropriate offset */
592    int from, to;	/* the from and to row/col */
593    int lo, hi;		/* the lo and hi col/row */
594    int outOfBounds;	/* the boundary check for shifting items */
595{
596    int j, new;
597    char buf[INDEX_BUFSIZE], buf1[INDEX_BUFSIZE];
598    Tcl_HashEntry *entryPtr, *newPtr;
599    TableEmbWindow *ewPtr;
600
601    /*
602     * move row/col style && width/height here
603     * If -holdtags is specified, we don't move the user-set widths/heights
604     * of the absolute rows/columns, otherwise we enter here to move the
605     * dimensions appropriately
606     */
607    if (!(flags & HOLD_TAGS)) {
608	entryPtr = Tcl_FindHashEntry(tagTblPtr, (char *)from);
609	if (entryPtr != NULL) {
610	    Tcl_DeleteHashEntry(entryPtr);
611	}
612	entryPtr = Tcl_FindHashEntry(dimTblPtr, (char *)from-offset);
613	if (entryPtr != NULL) {
614	    Tcl_DeleteHashEntry(entryPtr);
615	}
616	if (!outOfBounds) {
617	    entryPtr = Tcl_FindHashEntry(tagTblPtr, (char *)to);
618	    if (entryPtr != NULL) {
619		newPtr = Tcl_CreateHashEntry(tagTblPtr, (char *)from, &new);
620		Tcl_SetHashValue(newPtr, Tcl_GetHashValue(entryPtr));
621		Tcl_DeleteHashEntry(entryPtr);
622	    }
623	    entryPtr = Tcl_FindHashEntry(dimTblPtr, (char *)to-offset);
624	    if (entryPtr != NULL) {
625		newPtr = Tcl_CreateHashEntry(dimTblPtr, (char *)from-offset,
626			&new);
627		Tcl_SetHashValue(newPtr, Tcl_GetHashValue(entryPtr));
628		Tcl_DeleteHashEntry(entryPtr);
629	    }
630	}
631    }
632    for (j = lo; j <= hi; j++) {
633	if (doRows /* rows */) {
634	    TableMakeArrayIndex(from, j, buf);
635	    TableMakeArrayIndex(to, j, buf1);
636	    TableMoveCellValue(tablePtr, to, j, buf1, from, j, buf,
637		    outOfBounds);
638	} else {
639	    TableMakeArrayIndex(j, from, buf);
640	    TableMakeArrayIndex(j, to, buf1);
641	    TableMoveCellValue(tablePtr, j, to, buf1, j, from, buf,
642		    outOfBounds);
643	}
644	/*
645	 * If -holdselection is specified, we leave the selected cells in the
646	 * absolute cell values, otherwise we enter here to move the
647	 * selection appropriately
648	 */
649	if (!(flags & HOLD_SEL)) {
650	    entryPtr = Tcl_FindHashEntry(tablePtr->selCells, buf);
651	    if (entryPtr != NULL) {
652		Tcl_DeleteHashEntry(entryPtr);
653	    }
654	    if (!outOfBounds) {
655		entryPtr = Tcl_FindHashEntry(tablePtr->selCells, buf1);
656		if (entryPtr != NULL) {
657		    Tcl_CreateHashEntry(tablePtr->selCells, buf, &new);
658		    Tcl_DeleteHashEntry(entryPtr);
659		}
660	    }
661	}
662	/*
663	 * If -holdtags is specified, we leave the tags in the
664	 * absolute cell values, otherwise we enter here to move the
665	 * tags appropriately
666	 */
667	if (!(flags & HOLD_TAGS)) {
668	    entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf);
669	    if (entryPtr != NULL) {
670		Tcl_DeleteHashEntry(entryPtr);
671	    }
672	    if (!outOfBounds) {
673		entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf1);
674		if (entryPtr != NULL) {
675		    newPtr = Tcl_CreateHashEntry(tablePtr->cellStyles, buf,
676			    &new);
677		    Tcl_SetHashValue(newPtr, Tcl_GetHashValue(entryPtr));
678		    Tcl_DeleteHashEntry(entryPtr);
679		}
680	    }
681	}
682	/*
683	 * If -holdwindows is specified, we leave the windows in the
684	 * absolute cell values, otherwise we enter here to move the
685	 * windows appropriately
686	 */
687	if (!(flags & HOLD_WINS)) {
688	    /*
689	     * Delete whatever window might be in our destination
690	     */
691	    Table_WinDelete(tablePtr, buf);
692	    if (!outOfBounds) {
693		/*
694		 * buf1 is where the window is
695		 * buf is where we want it to be
696		 *
697		 * This is an adaptation of Table_WinMove, which we can't
698		 * use because we are intermediately fiddling with boundaries
699		 */
700		entryPtr = Tcl_FindHashEntry(tablePtr->winTable, buf1);
701		if (entryPtr != NULL) {
702		    /*
703		     * If there was a window in our source,
704		     * get the window pointer to move it
705		     */
706		    ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr);
707		    /* and free the old hash table entry */
708		    Tcl_DeleteHashEntry(entryPtr);
709
710		    entryPtr = Tcl_CreateHashEntry(tablePtr->winTable, buf,
711			    &new);
712		    /*
713		     * We needn't check if a window was in buf, since the
714		     * Table_WinDelete above should guarantee that no window
715		     * is there.  Just set the new entry's value.
716		     */
717		    Tcl_SetHashValue(entryPtr, (ClientData) ewPtr);
718		    ewPtr->hPtr = entryPtr;
719		}
720	    }
721	}
722    }
723}
724