diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 18:27:18 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 18:27:18 (GMT) |
commit | 32c31a3b172990fd736e0ed21bcab1e1d6ab6570 (patch) | |
tree | d36501971e32ebf2eb2c1c0d813e0af01d83a04a /generic/tkTableEdit.c | |
download | blt-32c31a3b172990fd736e0ed21bcab1e1d6ab6570.zip blt-32c31a3b172990fd736e0ed21bcab1e1d6ab6570.tar.gz blt-32c31a3b172990fd736e0ed21bcab1e1d6ab6570.tar.bz2 |
Squashed 'tktable/' content from commit 1429721
git-subtree-dir: tktable
git-subtree-split: 142972112150475defaaf03047d9cac2efe69662
Diffstat (limited to 'generic/tkTableEdit.c')
-rw-r--r-- | generic/tkTableEdit.c | 723 |
1 files changed, 723 insertions, 0 deletions
diff --git a/generic/tkTableEdit.c b/generic/tkTableEdit.c new file mode 100644 index 0000000..4c56710 --- /dev/null +++ b/generic/tkTableEdit.c @@ -0,0 +1,723 @@ +/* + * tkTableEdit.c -- + * + * This module implements editing functions of a table widget. + * + * Copyright (c) 1998-2000 Jeffrey Hobbs + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id: tkTableEdit.c,v 1.3 2016/01/27 19:43:23 joye Exp $ + */ + +#include "tkTable.h" + +static void TableModifyRC _ANSI_ARGS_((register Table *tablePtr, + int doRows, int movetag, + Tcl_HashTable *tagTblPtr, Tcl_HashTable *dimTblPtr, + int offset, int from, int to, int lo, int hi, + int outOfBounds)); + +/* insert/delete subcommands */ +static CONST84 char *modCmdNames[] = { + "active", "cols", "rows", (char *)NULL +}; +enum modCmd { + MOD_ACTIVE, MOD_COLS, MOD_ROWS +}; + +/* insert/delete row/col switches */ +static CONST84 char *rcCmdNames[] = { + "-keeptitles", "-holddimensions", "-holdselection", + "-holdtags", "-holdwindows", "--", + (char *) NULL +}; +enum rcCmd { + OPT_TITLES, OPT_DIMS, OPT_SEL, + OPT_TAGS, OPT_WINS, OPT_LAST +}; + +#define HOLD_TITLES 1<<0 +#define HOLD_DIMS 1<<1 +#define HOLD_TAGS 1<<2 +#define HOLD_WINS 1<<3 +#define HOLD_SEL 1<<4 + + +/* + *-------------------------------------------------------------- + * + * Table_EditCmd -- + * This procedure is invoked to process the insert/delete method + * that corresponds to a table widget managed by this module. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ +int +Table_EditCmd(ClientData clientData, register Tcl_Interp *interp, + int objc, Tcl_Obj *CONST objv[]) +{ + register Table *tablePtr = (Table *) clientData; + int doInsert, cmdIndex, first, last; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "option ?switches? arg ?arg?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[2], modCmdNames, + "option", 0, &cmdIndex) != TCL_OK) { + return TCL_ERROR; + } + + doInsert = (*(Tcl_GetString(objv[1])) == 'i'); + switch ((enum modCmd) cmdIndex) { + case MOD_ACTIVE: + if (doInsert) { + /* INSERT */ + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "index string"); + return TCL_ERROR; + } + if (TableGetIcursorObj(tablePtr, objv[3], &first) != TCL_OK) { + return TCL_ERROR; + } else if ((tablePtr->flags & HAS_ACTIVE) && + !(tablePtr->flags & ACTIVE_DISABLED) && + tablePtr->state == STATE_NORMAL) { + TableInsertChars(tablePtr, first, Tcl_GetString(objv[4])); + } + } else { + /* DELETE */ + if (objc > 5) { + Tcl_WrongNumArgs(interp, 3, objv, "first ?last?"); + return TCL_ERROR; + } + if (TableGetIcursorObj(tablePtr, objv[3], &first) != TCL_OK) { + return TCL_ERROR; + } + if (objc == 4) { + last = first+1; + } else if (TableGetIcursorObj(tablePtr, objv[4], + &last) != TCL_OK) { + return TCL_ERROR; + } + if ((last >= first) && (tablePtr->flags & HAS_ACTIVE) && + !(tablePtr->flags & ACTIVE_DISABLED) && + tablePtr->state == STATE_NORMAL) { + TableDeleteChars(tablePtr, first, last-first); + } + } + break; /* EDIT ACTIVE */ + + case MOD_COLS: + case MOD_ROWS: { + /* + * ROW/COL INSERTION/DELETION + * FIX: This doesn't handle spans + */ + int i, lo, hi, argsLeft, offset, minkeyoff, doRows; + int maxrow, maxcol, maxkey, minkey, flags, count, *dimPtr; + Tcl_HashTable *tagTblPtr, *dimTblPtr; + Tcl_HashSearch search; + + doRows = (cmdIndex == MOD_ROWS); + flags = 0; + for (i = 3; i < objc; i++) { + if (*(Tcl_GetString(objv[i])) != '-') { + break; + } + if (Tcl_GetIndexFromObj(interp, objv[i], rcCmdNames, + "switch", 0, &cmdIndex) != TCL_OK) { + return TCL_ERROR; + } + if (cmdIndex == OPT_LAST) { + i++; + break; + } + switch (cmdIndex) { + case OPT_TITLES: + flags |= HOLD_TITLES; + break; + case OPT_DIMS: + flags |= HOLD_DIMS; + break; + case OPT_SEL: + flags |= HOLD_SEL; + break; + case OPT_TAGS: + flags |= HOLD_TAGS; + break; + case OPT_WINS: + flags |= HOLD_WINS; + break; + } + } + argsLeft = objc - i; + if (argsLeft < 1 || argsLeft > 2) { + Tcl_WrongNumArgs(interp, 3, objv, "?switches? index ?count?"); + return TCL_ERROR; + } + + count = 1; + maxcol = tablePtr->cols-1+tablePtr->colOffset; + maxrow = tablePtr->rows-1+tablePtr->rowOffset; + if (strcmp(Tcl_GetString(objv[i]), "end") == 0) { + /* allow "end" to be specified as an index */ + first = (doRows) ? maxrow : maxcol; + } else if (Tcl_GetIntFromObj(interp, objv[i], &first) != TCL_OK) { + return TCL_ERROR; + } + if (argsLeft == 2 && + Tcl_GetIntFromObj(interp, objv[++i], &count) != TCL_OK) { + return TCL_ERROR; + } + if (count == 0 || (tablePtr->state == STATE_DISABLED)) { + return TCL_OK; + } + + if (doRows) { + maxkey = maxrow; + minkey = tablePtr->rowOffset; + minkeyoff = tablePtr->rowOffset+tablePtr->titleRows; + offset = tablePtr->rowOffset; + tagTblPtr = tablePtr->rowStyles; + dimTblPtr = tablePtr->rowHeights; + dimPtr = &(tablePtr->rows); + lo = tablePtr->colOffset + + ((flags & HOLD_TITLES) ? tablePtr->titleCols : 0); + hi = maxcol; + } else { + maxkey = maxcol; + minkey = tablePtr->colOffset; + minkeyoff = tablePtr->colOffset+tablePtr->titleCols; + offset = tablePtr->colOffset; + tagTblPtr = tablePtr->colStyles; + dimTblPtr = tablePtr->colWidths; + dimPtr = &(tablePtr->cols); + lo = tablePtr->rowOffset + + ((flags & HOLD_TITLES) ? tablePtr->titleRows : 0); + hi = maxrow; + } + + /* constrain the starting index */ + if (first > maxkey) { + first = maxkey; + } else if (first < minkey) { + first = minkey; + } + if (doInsert) { + /* +count means insert after index, + * -count means insert before index */ + if (count < 0) { + count = -count; + } else { + first++; + } + if ((flags & HOLD_TITLES) && (first < minkeyoff)) { + count -= minkeyoff-first; + if (count <= 0) { + return TCL_OK; + } + first = minkeyoff; + } + if (!(flags & HOLD_DIMS)) { + maxkey += count; + *dimPtr += count; + } + /* + * We need to call TableAdjustParams before TableModifyRC to + * ensure that side effect code like var traces that might get + * called will access the correct new dimensions. + */ + if (*dimPtr < 1) { + *dimPtr = 1; + } + TableAdjustParams(tablePtr); + for (i = maxkey; i >= first; i--) { + /* move row/col style && width/height here */ + TableModifyRC(tablePtr, doRows, flags, tagTblPtr, dimTblPtr, + offset, i, i-count, lo, hi, ((i-count) < first)); + } + if (!(flags & HOLD_WINS)) { + /* + * This may be a little severe, but it does unmap the + * windows that need to be unmapped, and those that should + * stay do remap correctly. [Bug #551325] + */ + if (doRows) { + EmbWinUnmap(tablePtr, + first - tablePtr->rowOffset, + maxkey - tablePtr->rowOffset, + lo - tablePtr->colOffset, + hi - tablePtr->colOffset); + } else { + EmbWinUnmap(tablePtr, + lo - tablePtr->rowOffset, + hi - tablePtr->rowOffset, + first - tablePtr->colOffset, + maxkey - tablePtr->colOffset); + } + } + } else { + /* (index = i && count = 1) == (index = i && count = -1) */ + if (count < 0) { + /* if the count is negative, make sure that the col count will + * delete no greater than the original index */ + if (first+count < minkey) { + if (first-minkey < abs(count)) { + /* + * In this case, the user is asking to delete more rows + * than exist before the minkey, so we have to shrink + * the count down to the existing rows up to index. + */ + count = first-minkey; + } else { + count += first-minkey; + } + first = minkey; + } else { + first += count; + count = -count; + } + } + if ((flags & HOLD_TITLES) && (first <= minkeyoff)) { + count -= minkeyoff-first; + if (count <= 0) { + return TCL_OK; + } + first = minkeyoff; + } + if (count > maxkey-first+1) { + count = maxkey-first+1; + } + if (!(flags & HOLD_DIMS)) { + *dimPtr -= count; + } + /* + * We need to call TableAdjustParams before TableModifyRC to + * ensure that side effect code like var traces that might get + * called will access the correct new dimensions. + */ + if (*dimPtr < 1) { + *dimPtr = 1; + } + TableAdjustParams(tablePtr); + for (i = first; i <= maxkey; i++) { + TableModifyRC(tablePtr, doRows, flags, tagTblPtr, dimTblPtr, + offset, i, i+count, lo, hi, ((i+count) > maxkey)); + } + } + if (!(flags & HOLD_SEL) && + Tcl_FirstHashEntry(tablePtr->selCells, &search) != NULL) { + /* clear selection - forceful, but effective */ + Tcl_DeleteHashTable(tablePtr->selCells); + Tcl_InitHashTable(tablePtr->selCells, TCL_STRING_KEYS); + } + + /* + * Make sure that the modified dimension is actually legal + * after removing all that stuff. + */ + if (*dimPtr < 1) { + *dimPtr = 1; + TableAdjustParams(tablePtr); + } + + /* change the geometry */ + TableGeometryRequest(tablePtr); + /* FIX: + * This has to handle when the previous rows/cols resize because + * of the *stretchmode. InvalidateAll does that, but could be + * more efficient. + */ + TableInvalidateAll(tablePtr, 0); + break; + } + + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TableDeleteChars -- + * Remove one or more characters from an table widget. + * + * Results: + * None. + * + * Side effects: + * Memory gets freed, the table gets modified and (eventually) + * redisplayed. + * + *---------------------------------------------------------------------- + */ +void +TableDeleteChars(tablePtr, index, count) + register Table *tablePtr; /* Table widget to modify. */ + int index; /* Index of first character to delete. */ + int count; /* How many characters to delete. */ +{ +#ifdef TCL_UTF_MAX + int byteIndex, byteCount, newByteCount, numBytes, numChars; + char *new, *string; + + string = tablePtr->activeBuf; + numBytes = strlen(string); + numChars = Tcl_NumUtfChars(string, numBytes); + if ((index + count) > numChars) { + count = numChars - index; + } + if (count <= 0) { + return; + } + + byteIndex = Tcl_UtfAtIndex(string, index) - string; + byteCount = Tcl_UtfAtIndex(string + byteIndex, count) + - (string + byteIndex); + + newByteCount = numBytes + 1 - byteCount; + new = (char *) ckalloc((unsigned) newByteCount); + memcpy(new, string, (size_t) byteIndex); + strcpy(new + byteIndex, string + byteIndex + byteCount); +#else + int oldlen; + char *new; + + /* this gets the length of the string, as well as ensuring that + * the cursor isn't beyond the end char */ + TableGetIcursor(tablePtr, "end", &oldlen); + + if ((index+count) > oldlen) + count = oldlen-index; + if (count <= 0) + return; + + new = (char *) ckalloc((unsigned)(oldlen-count+1)); + strncpy(new, tablePtr->activeBuf, (size_t) index); + strcpy(new+index, tablePtr->activeBuf+index+count); + /* make sure this string is null terminated */ + new[oldlen-count] = '\0'; +#endif + /* This prevents deletes on BREAK or validation error. */ + if (tablePtr->validate && + TableValidateChange(tablePtr, tablePtr->activeRow+tablePtr->rowOffset, + tablePtr->activeCol+tablePtr->colOffset, + tablePtr->activeBuf, new, index) != TCL_OK) { + ckfree(new); + return; + } + + ckfree(tablePtr->activeBuf); + tablePtr->activeBuf = new; + + /* mark the text as changed */ + tablePtr->flags |= TEXT_CHANGED; + + if (tablePtr->icursor >= index) { + if (tablePtr->icursor >= (index+count)) { + tablePtr->icursor -= count; + } else { + tablePtr->icursor = index; + } + } + + TableSetActiveIndex(tablePtr); + + TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); +} + +/* + *---------------------------------------------------------------------- + * + * TableInsertChars -- + * Add new characters to the active cell of a table widget. + * + * Results: + * None. + * + * Side effects: + * New information gets added to tablePtr; it will be redisplayed + * soon, but not necessarily immediately. + * + *---------------------------------------------------------------------- + */ +void +TableInsertChars(tablePtr, index, value) + register Table *tablePtr; /* Table that is to get the new elements. */ + int index; /* Add the new elements before this element. */ + char *value; /* New characters to add (NULL-terminated + * string). */ +{ +#ifdef TCL_UTF_MAX + int oldlen, byteIndex, byteCount; + char *new, *string; + + byteCount = strlen(value); + if (byteCount == 0) { + return; + } + + /* Is this an autoclear and this is the first update */ + /* Note that this clears without validating */ + if (tablePtr->autoClear && !(tablePtr->flags & TEXT_CHANGED)) { + /* set the buffer to be empty */ + tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, 1); + tablePtr->activeBuf[0] = '\0'; + /* the insert position now has to be 0 */ + index = 0; + tablePtr->icursor = 0; + } + + string = tablePtr->activeBuf; + byteIndex = Tcl_UtfAtIndex(string, index) - string; + + oldlen = strlen(string); + new = (char *) ckalloc((unsigned)(oldlen + byteCount + 1)); + memcpy(new, string, (size_t) byteIndex); + strcpy(new + byteIndex, value); + strcpy(new + byteIndex + byteCount, string + byteIndex); + + /* validate potential new active buffer */ + /* This prevents inserts on either BREAK or validation error. */ + if (tablePtr->validate && + TableValidateChange(tablePtr, tablePtr->activeRow+tablePtr->rowOffset, + tablePtr->activeCol+tablePtr->colOffset, + tablePtr->activeBuf, new, byteIndex) != TCL_OK) { + ckfree(new); + return; + } + + /* + * The following construction is used because inserting improperly + * formed UTF-8 sequences between other improperly formed UTF-8 + * sequences could result in actually forming valid UTF-8 sequences; + * the number of characters added may not be Tcl_NumUtfChars(string, -1), + * because of context. The actual number of characters added is how + * many characters were are in the string now minus the number that + * used to be there. + */ + + if (tablePtr->icursor >= index) { + tablePtr->icursor += Tcl_NumUtfChars(new, oldlen+byteCount) + - Tcl_NumUtfChars(tablePtr->activeBuf, oldlen); + } + + ckfree(string); + tablePtr->activeBuf = new; + +#else + int oldlen, newlen; + char *new; + + newlen = strlen(value); + if (newlen == 0) return; + + /* Is this an autoclear and this is the first update */ + /* Note that this clears without validating */ + if (tablePtr->autoClear && !(tablePtr->flags & TEXT_CHANGED)) { + /* set the buffer to be empty */ + tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, 1); + tablePtr->activeBuf[0] = '\0'; + /* the insert position now has to be 0 */ + index = 0; + } + oldlen = strlen(tablePtr->activeBuf); + /* get the buffer to at least the right length */ + new = (char *) ckalloc((unsigned)(oldlen+newlen+1)); + strncpy(new, tablePtr->activeBuf, (size_t) index); + strcpy(new+index, value); + strcpy(new+index+newlen, (tablePtr->activeBuf)+index); + /* make sure this string is null terminated */ + new[oldlen+newlen] = '\0'; + + /* validate potential new active buffer */ + /* This prevents inserts on either BREAK or validation error. */ + if (tablePtr->validate && + TableValidateChange(tablePtr, tablePtr->activeRow+tablePtr->rowOffset, + tablePtr->activeCol+tablePtr->colOffset, + tablePtr->activeBuf, new, index) != TCL_OK) { + ckfree(new); + return; + } + ckfree(tablePtr->activeBuf); + tablePtr->activeBuf = new; + + if (tablePtr->icursor >= index) { + tablePtr->icursor += newlen; + } +#endif + + /* mark the text as changed */ + tablePtr->flags |= TEXT_CHANGED; + + TableSetActiveIndex(tablePtr); + + TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); +} + +/* + *---------------------------------------------------------------------- + * + * TableModifyRC -- + * Helper function that does the core work of moving rows/cols + * and associated tags. + * + * Results: + * None. + * + * Side effects: + * Moves cell data and possibly tag data + * + *---------------------------------------------------------------------- + */ +static void +TableModifyRC(tablePtr, doRows, flags, tagTblPtr, dimTblPtr, + offset, from, to, lo, hi, outOfBounds) + Table *tablePtr; /* Information about text widget. */ + int doRows; /* rows (1) or cols (0) */ + int flags; /* flags indicating what to move */ + Tcl_HashTable *tagTblPtr, *dimTblPtr; /* Pointers to the row/col tags + * and width/height tags */ + int offset; /* appropriate offset */ + int from, to; /* the from and to row/col */ + int lo, hi; /* the lo and hi col/row */ + int outOfBounds; /* the boundary check for shifting items */ +{ + int j, new; + char buf[INDEX_BUFSIZE], buf1[INDEX_BUFSIZE]; + Tcl_HashEntry *entryPtr, *newPtr; + TableEmbWindow *ewPtr; + + /* + * move row/col style && width/height here + * If -holdtags is specified, we don't move the user-set widths/heights + * of the absolute rows/columns, otherwise we enter here to move the + * dimensions appropriately + */ + if (!(flags & HOLD_TAGS)) { + entryPtr = Tcl_FindHashEntry(tagTblPtr, (char *)from); + if (entryPtr != NULL) { + Tcl_DeleteHashEntry(entryPtr); + } + entryPtr = Tcl_FindHashEntry(dimTblPtr, (char *)from-offset); + if (entryPtr != NULL) { + Tcl_DeleteHashEntry(entryPtr); + } + if (!outOfBounds) { + entryPtr = Tcl_FindHashEntry(tagTblPtr, (char *)to); + if (entryPtr != NULL) { + newPtr = Tcl_CreateHashEntry(tagTblPtr, (char *)from, &new); + Tcl_SetHashValue(newPtr, Tcl_GetHashValue(entryPtr)); + Tcl_DeleteHashEntry(entryPtr); + } + entryPtr = Tcl_FindHashEntry(dimTblPtr, (char *)to-offset); + if (entryPtr != NULL) { + newPtr = Tcl_CreateHashEntry(dimTblPtr, (char *)from-offset, + &new); + Tcl_SetHashValue(newPtr, Tcl_GetHashValue(entryPtr)); + Tcl_DeleteHashEntry(entryPtr); + } + } + } + for (j = lo; j <= hi; j++) { + if (doRows /* rows */) { + TableMakeArrayIndex(from, j, buf); + TableMakeArrayIndex(to, j, buf1); + TableMoveCellValue(tablePtr, to, j, buf1, from, j, buf, + outOfBounds); + } else { + TableMakeArrayIndex(j, from, buf); + TableMakeArrayIndex(j, to, buf1); + TableMoveCellValue(tablePtr, j, to, buf1, j, from, buf, + outOfBounds); + } + /* + * If -holdselection is specified, we leave the selected cells in the + * absolute cell values, otherwise we enter here to move the + * selection appropriately + */ + if (!(flags & HOLD_SEL)) { + entryPtr = Tcl_FindHashEntry(tablePtr->selCells, buf); + if (entryPtr != NULL) { + Tcl_DeleteHashEntry(entryPtr); + } + if (!outOfBounds) { + entryPtr = Tcl_FindHashEntry(tablePtr->selCells, buf1); + if (entryPtr != NULL) { + Tcl_CreateHashEntry(tablePtr->selCells, buf, &new); + Tcl_DeleteHashEntry(entryPtr); + } + } + } + /* + * If -holdtags is specified, we leave the tags in the + * absolute cell values, otherwise we enter here to move the + * tags appropriately + */ + if (!(flags & HOLD_TAGS)) { + entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf); + if (entryPtr != NULL) { + Tcl_DeleteHashEntry(entryPtr); + } + if (!outOfBounds) { + entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf1); + if (entryPtr != NULL) { + newPtr = Tcl_CreateHashEntry(tablePtr->cellStyles, buf, + &new); + Tcl_SetHashValue(newPtr, Tcl_GetHashValue(entryPtr)); + Tcl_DeleteHashEntry(entryPtr); + } + } + } + /* + * If -holdwindows is specified, we leave the windows in the + * absolute cell values, otherwise we enter here to move the + * windows appropriately + */ + if (!(flags & HOLD_WINS)) { + /* + * Delete whatever window might be in our destination + */ + Table_WinDelete(tablePtr, buf); + if (!outOfBounds) { + /* + * buf1 is where the window is + * buf is where we want it to be + * + * This is an adaptation of Table_WinMove, which we can't + * use because we are intermediately fiddling with boundaries + */ + entryPtr = Tcl_FindHashEntry(tablePtr->winTable, buf1); + if (entryPtr != NULL) { + /* + * If there was a window in our source, + * get the window pointer to move it + */ + ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr); + /* and free the old hash table entry */ + Tcl_DeleteHashEntry(entryPtr); + + entryPtr = Tcl_CreateHashEntry(tablePtr->winTable, buf, + &new); + /* + * We needn't check if a window was in buf, since the + * Table_WinDelete above should guarantee that no window + * is there. Just set the new entry's value. + */ + Tcl_SetHashValue(entryPtr, (ClientData) ewPtr); + ewPtr->hPtr = entryPtr; + } + } + } + } +} |