diff options
author | hobbs <hobbs> | 2001-11-13 00:19:05 (GMT) |
---|---|---|
committer | hobbs <hobbs> | 2001-11-13 00:19:05 (GMT) |
commit | e3690695eaccf83b8076fd877d17ed591c43065d (patch) | |
tree | 65c1489fbcc7a86e2140be2d55b1ffd0ccbd2577 /generic | |
parent | 3027f28d1f2411602324f0964f7f1ad28742cf93 (diff) | |
download | tk-e3690695eaccf83b8076fd877d17ed591c43065d.zip tk-e3690695eaccf83b8076fd877d17ed591c43065d.tar.gz tk-e3690695eaccf83b8076fd877d17ed591c43065d.tar.bz2 |
added TIP#26 text widget undo/redo functionality
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tkText.c | 663 | ||||
-rw-r--r-- | generic/tkText.h | 55 | ||||
-rw-r--r-- | generic/tkTextTag.c | 45 |
3 files changed, 682 insertions, 81 deletions
diff --git a/generic/tkText.c b/generic/tkText.c index ef1ae4d..a19b984 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -14,7 +14,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.c,v 1.18 2000/11/22 01:49:38 ericm Exp $ + * RCS: @(#) $Id: tkText.c,v 1.19 2001/11/13 00:19:05 hobbs Exp $ */ #include "default.h" @@ -42,6 +42,9 @@ static Tk_CustomOption stateOption = { */ static Tk_ConfigSpec configSpecs[] = { + {TK_CONFIG_BOOLEAN, "-autoseparators", "autoSeparators", + "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, + Tk_Offset(TkText, autoSeparators), 0}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", @@ -129,6 +132,8 @@ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_TEXT_TAKE_FOCUS, Tk_Offset(TkText, takeFocus), TK_CONFIG_NULL_OK}, + {TK_CONFIG_BOOLEAN, "-undo", "undo", "Undo", + DEF_TEXT_UNDO, Tk_Offset(TkText, undo), 0}, {TK_CONFIG_INT, "-width", "width", "Width", DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0}, {TK_CONFIG_CUSTOM, "-wrap", "wrap", "Wrap", @@ -292,6 +297,8 @@ static int TextFetchSelection _ANSI_ARGS_((ClientData clientData, int offset, char *buffer, int maxBytes)); static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int argc, char **argv)); +static int TextEditCmd _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, int argc, char **argv)); static int TextWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); static void TextWorldChanged _ANSI_ARGS_(( @@ -304,6 +311,19 @@ static void DumpLine _ANSI_ARGS_((Tcl_Interp *interp, static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, char *key, char *value, char * command, TkTextIndex *index, int what)); +static int TextEditUndo _ANSI_ARGS_((Tcl_Interp * interp, + TkText *textPtr)); +static int TextEditRedo _ANSI_ARGS_((Tcl_Interp * interp, + TkText *textPtr)); +static char * TextGetText _ANSI_ARGS_((TkTextIndex * index1, + TkTextIndex * index2)); +static void pushStack _ANSI_ARGS_(( TkTextEditAtom ** stack, + TkTextEditAtom * elem )); + +static TkTextEditAtom * popStack _ANSI_ARGS_((TkTextEditAtom ** stack)); +static void clearStack _ANSI_ARGS_((TkTextEditAtom ** stack)); +static void insertSeparator _ANSI_ARGS_((TkTextEditAtom ** stack)); +static void updateDirtyFlag _ANSI_ARGS_((TkText *textPtr)); /* * The structure below defines text class behavior by means of procedures @@ -361,7 +381,14 @@ Tk_TextCmd(clientData, interp, argc, argv) return TCL_ERROR; } + /* + * Create the text widget and initialize everything to zero, + * then set the necessary initial (non-NULL) values. + */ + textPtr = (TkText *) ckalloc(sizeof(TkText)); + memset((VOID *) textPtr, 0, sizeof(TkText)); + textPtr->tkwin = new; textPtr->display = Tk_Display(new); textPtr->interp = interp; @@ -370,61 +397,24 @@ Tk_TextCmd(clientData, interp, argc, argv) (ClientData) textPtr, TextCmdDeletedProc); textPtr->tree = TkBTreeCreate(textPtr); Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS); - textPtr->numTags = 0; Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS); Tcl_InitHashTable(&textPtr->windowTable, TCL_STRING_KEYS); Tcl_InitHashTable(&textPtr->imageTable, TCL_STRING_KEYS); textPtr->state = TK_STATE_NORMAL; - textPtr->border = NULL; - textPtr->borderWidth = 0; - textPtr->padX = 0; - textPtr->padY = 0; textPtr->relief = TK_RELIEF_FLAT; - textPtr->highlightWidth = 0; - textPtr->highlightBgColorPtr = NULL; - textPtr->highlightColorPtr = NULL; textPtr->cursor = None; - textPtr->fgColor = NULL; - textPtr->tkfont = NULL; textPtr->charWidth = 1; - textPtr->spacing1 = 0; - textPtr->spacing2 = 0; - textPtr->spacing3 = 0; - textPtr->tabOptionString = NULL; - textPtr->tabArrayPtr = NULL; textPtr->wrapMode = TEXT_WRAPMODE_CHAR; - textPtr->width = 0; - textPtr->height = 0; - textPtr->setGrid = 0; textPtr->prevWidth = Tk_Width(new); textPtr->prevHeight = Tk_Height(new); TkTextCreateDInfo(textPtr); TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex); TkTextSetYView(textPtr, &startIndex, 0); - textPtr->selTagPtr = NULL; - textPtr->selBorder = NULL; - textPtr->selBdString = NULL; - textPtr->selFgColorPtr = NULL; textPtr->exportSelection = 1; - textPtr->abortSelections = 0; - textPtr->insertMarkPtr = NULL; - textPtr->insertBorder = NULL; - textPtr->insertWidth = 0; - textPtr->insertBorderWidth = 0; - textPtr->insertOnTime = 0; - textPtr->insertOffTime = 0; - textPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; - textPtr->bindingTable = NULL; - textPtr->currentMarkPtr = NULL; textPtr->pickEvent.type = LeaveNotify; - textPtr->pickEvent.xcrossing.x = 0; - textPtr->pickEvent.xcrossing.y = 0; - textPtr->numCurTags = 0; - textPtr->curTagArrayPtr = NULL; - textPtr->takeFocus = NULL; - textPtr->xScrollCmd = NULL; - textPtr->yScrollCmd = NULL; - textPtr->flags = 0; + textPtr->undo = 1; + textPtr->isDirtyIncrement = 1; + textPtr->autoSeparators = 1; /* * Create the "sel" tag and the "current" and "insert" marks. @@ -488,6 +478,7 @@ TextWidgetCmd(clientData, interp, argc, argv) size_t length; int c; TkTextIndex index1, index2; + char * string; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", @@ -635,6 +626,8 @@ TextWidgetCmd(clientData, interp, argc, argv) sprintf(buf, "%d %d %d %d %d", x, y, width, height, base); Tcl_SetResult(interp, buf, TCL_VOLATILE); } + } else if ((c == 'e') && (strncmp(argv[1], "edit", length) == 0)) { + result = TextEditCmd(textPtr, interp, argc, argv); } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) { if ((argc != 3) && (argc != 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", @@ -657,32 +650,10 @@ TextWidgetCmd(clientData, interp, argc, argv) if (TkTextIndexCmp(&index1, &index2) >= 0) { goto done; } - while (1) { - int offset, last, savedChar; - TkTextSegment *segPtr; - - segPtr = TkTextIndexToSeg(&index1, &offset); - last = segPtr->size; - if (index1.linePtr == index2.linePtr) { - int last2; - - if (index2.byteIndex == index1.byteIndex) { - break; - } - last2 = index2.byteIndex - index1.byteIndex + offset; - if (last2 < last) { - last = last2; - } - } - if (segPtr->typePtr == &tkTextCharType) { - savedChar = segPtr->body.chars[last]; - segPtr->body.chars[last] = 0; - Tcl_AppendResult(interp, segPtr->body.chars + offset, - (char *) NULL); - segPtr->body.chars[last] = savedChar; - } - TkTextIndexForwBytes(&index1, last-offset, &index1); - } + string = TextGetText(&index1,&index2); + Tcl_AppendResult(interp, string, (char *) NULL); + ckfree(string); + } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0) && (length >= 3)) { char buf[200]; @@ -770,8 +741,8 @@ TextWidgetCmd(clientData, interp, argc, argv) } else { Tcl_AppendResult(interp, "bad option \"", argv[1], "\": must be bbox, cget, compare, configure, debug, delete, ", - "dlineinfo, dump, get, image, index, insert, mark, scan, ", - "search, see, tag, window, xview, or yview", + "dlineinfo, dump, edit, get, image, index, insert, mark, ", + "scan, search, see, tag, window, xview, or yview", (char *) NULL); result = TCL_ERROR; } @@ -1208,6 +1179,8 @@ InsertChars(textPtr, indexPtr, string) { int lineIndex, resetView, offset; TkTextIndex newTop; + TkTextEditAtom * insertion; + char indexBuffer[TK_POS_CHARS]; /* * Don't allow insertions on the last (dummy) line of the text. @@ -1236,6 +1209,32 @@ InsertChars(textPtr, indexPtr, string) } TkTextChanged(textPtr, indexPtr, indexPtr); TkBTreeInsertChars(indexPtr, string); + + /* + * Push the insertion on the undo stack + */ + + if ( textPtr->undo ) { + if (textPtr->autoSeparators && textPtr->undoStack && + textPtr->undoStack->type != INSERT) { + insertSeparator(&(textPtr->undoStack)); + } + + insertion = (TkTextEditAtom *) ckalloc(sizeof(TkTextEditAtom)); + insertion->type = INSERT; + + TkTextPrintIndex(indexPtr,indexBuffer); + insertion->index = (char *) ckalloc(strlen(indexBuffer) + 1); + strcpy(insertion->index,indexBuffer); + + insertion->string = (char *) ckalloc(strlen(string) + 1); + strcpy(insertion->string,string); + + pushStack(&(textPtr->undoStack),insertion); + clearStack(&(textPtr->redoStack)); + } + updateDirtyFlag(textPtr); + if (resetView) { TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop); TkTextIndexForwBytes(&newTop, offset, &newTop); @@ -1279,6 +1278,8 @@ DeleteChars(textPtr, index1String, index2String) { int line1, line2, line, byteIndex, resetView; TkTextIndex index1, index2; + TkTextEditAtom * deletion; + char indexBuffer[TK_POS_CHARS]; /* * Parse the starting and stopping indices. @@ -1390,6 +1391,31 @@ DeleteChars(textPtr, index1String, index2String) byteIndex -= (index2.byteIndex - index1.byteIndex); } } + + /* + * Push the deletion on the undo stack + */ + + if ( textPtr->undo ) { + if (textPtr->autoSeparators && (textPtr->undoStack != NULL) + && (textPtr->undoStack->type != DELETE)) { + insertSeparator(&(textPtr->undoStack)); + } + + deletion = (TkTextEditAtom *) ckalloc(sizeof(TkTextEditAtom)); + deletion->type = DELETE; + + TkTextPrintIndex(&index1,indexBuffer); + deletion->index = (char *) ckalloc(strlen(indexBuffer) + 1); + strcpy(deletion->index,indexBuffer); + + deletion->string = TextGetText(&index1, &index2); + + pushStack(&(textPtr->undoStack),deletion); + clearStack(&(textPtr->redoStack)); + } + updateDirtyFlag(textPtr); + TkBTreeDeleteChars(&index1, &index2); if (resetView) { TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1); @@ -1569,6 +1595,7 @@ TkTextLostSelection(clientData) ClientData clientData; /* Information about text widget. */ { register TkText *textPtr = (TkText *) clientData; + XEvent event; #ifdef ALWAYS_SHOW_SELECTION TkTextIndex start, end; @@ -1587,6 +1614,21 @@ TkTextLostSelection(clientData) TkTextRedrawTag(textPtr, &start, &end, textPtr->selTagPtr, 1); TkBTreeTag(&start, &end, textPtr->selTagPtr, 0); #endif + + /* + * Send an event that the selection changed. This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + textPtr->flags &= ~GOT_SELECTION; } @@ -2319,9 +2361,9 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) offset += segPtr->size, segPtr = segPtr->nextPtr) { if ((what & TK_DUMP_TEXT) && (segPtr->typePtr == &tkTextCharType) && (offset + segPtr->size > startByte)) { - char savedChar; /* Last char used in the seg */ - int last = segPtr->size; /* Index of savedChar */ - int first = 0; /* Index of first char in seg */ + char savedChar; /* Last char used in the seg */ + int last = segPtr->size; /* Index of savedChar */ + int first = 0; /* Index of first char in seg */ if (offset + segPtr->size > endByte) { last = endByte - offset; } @@ -2419,4 +2461,481 @@ DumpSegment(interp, key, value, command, index, what) return result; } } + +/* + * pushStack + * Push elem on the stack identified by stack. + * + * Results: + * None + * + * Side effects: + * None. + */ + +static void pushStack ( stack, elem ) + TkTextEditAtom ** stack; + TkTextEditAtom * elem; +{ + elem->next = *stack; + *stack = elem; +} + +/* + * popStack -- + * Remove and return the top element from the stack identified by + * stack. + * + * Results: + * None + * + * Side effects: + * None. + */ + +TkTextEditAtom * popStack ( stack ) + TkTextEditAtom ** stack ; +{ + TkTextEditAtom * elem = NULL; + if (*stack != NULL ) { + elem = *stack; + *stack = elem->next; + } + return elem; +} + +/* + * insertSeparator -- + * insert a separator on the stack, indicating a border for + * an undo/redo chunk. + * + * Results: + * None + * + * Side effects: + * None. + */ + +static void insertSeparator ( stack ) + TkTextEditAtom ** stack; +{ + TkTextEditAtom * separator; + + if ( *stack != NULL && (*stack)->type != SEPARATOR ) { + separator = (TkTextEditAtom *) ckalloc(sizeof(TkTextEditAtom)); + separator->type = SEPARATOR; + pushStack(stack,separator); + } +} + +/* + * clearStack -- + * Clear an entire undo or redo stack and destroy all elements in it. + * + * Results: + * None + * + * Side effects: + * None. + */ + +static void clearStack ( stack ) + TkTextEditAtom ** stack; /* An Undo or Redo stack */ +{ + TkTextEditAtom * elem; + + while ( (elem = popStack(stack)) ) { + if ( elem->type != SEPARATOR ) { + ckfree(elem->index); + ckfree(elem->string); + } + ckfree((char *)elem); + } + *stack = NULL; +} + +/* + * TextEditUndo -- + * undo the last change. + * + * Results: + * None + * + * Side effects: + * None. + */ + +static int TextEditUndo (interp,textPtr) + Tcl_Interp * interp; + TkText * textPtr; /* Overall information about text widget. */ +{ + TkTextEditAtom * elem; + TkTextIndex fromIndex, toIndex; + char buffer[TK_POS_CHARS]; + char viewIndex[TK_POS_CHARS]; + + if ( ! textPtr->undo ) { + return TCL_OK; + } + + /* Turn off the undo feature */ + + textPtr->undo = 0; + + /* insert a separator on the redo stack */ + + insertSeparator(&(textPtr->redoStack)); + + /* Pop and skip the first separator if there is one*/ + + elem = popStack(&(textPtr->undoStack)); + + if ( elem == NULL ) { + textPtr->undo = 1; + return TCL_ERROR; + } + + if ( ( elem != NULL ) && ( elem->type == SEPARATOR ) ) { + ckfree((char *) elem); + elem = popStack(&(textPtr->undoStack)); + } + + while ( elem && (elem->type != SEPARATOR) ) { + switch ( elem->type ) { + case INSERT: + TkTextGetIndex(interp,textPtr,elem->index,&toIndex); + strcpy(viewIndex,elem->index); + TkTextIndexForwBytes(&toIndex,(int)strlen(elem->string),&toIndex); + TkTextPrintIndex(&toIndex,buffer); + textPtr->isDirtyIncrement = -1; + DeleteChars(textPtr,elem->index,buffer); + textPtr->isDirtyIncrement = 1; + break; + case DELETE: + TkTextGetIndex(interp,textPtr,elem->index,&fromIndex); + textPtr->isDirtyIncrement = -1; + InsertChars(textPtr,&fromIndex,elem->string); + TkTextIndexForwBytes(&fromIndex,(int)strlen(elem->string),&toIndex); + TkTextPrintIndex(&toIndex,viewIndex); + textPtr->isDirtyIncrement = 1; + break; + default: + return TCL_ERROR; + } + pushStack(&(textPtr->redoStack),elem); + elem = popStack(&(textPtr->undoStack)); + } + + /* view the last changed position */ + + TkTextGetIndex(interp,textPtr,viewIndex,&toIndex); + TkTextSetMark(textPtr, "insert", &toIndex); + + /* insert a separator on the undo stack */ + + insertSeparator(&(textPtr->undoStack)); + + /* Turn back on the undo feature */ + + textPtr->undo = 1; + + return TCL_OK; +} + +/* + * TextEditRedo -- + * redo the last undone change. + * + * Results: + * None + * + * Side effects: + * None. + */ + +static int TextEditRedo (interp,textPtr) + Tcl_Interp * interp; + TkText * textPtr; /* Overall information about text widget. */ +{ + TkTextEditAtom *elem; + TkTextIndex fromIndex, toIndex; + char buffer[TK_POS_CHARS]; + char viewIndex[TK_POS_CHARS]; + + if (!textPtr->undo) { + return TCL_OK; + } + + /* Turn off the undo feature temporarily */ + + textPtr->undo = 0; + + /* insert a separator on the undo stack */ + + insertSeparator(&(textPtr->undoStack)); + + /* Pop and skip the first separator if there is one*/ + + elem = popStack(&(textPtr->redoStack)); + + if ( elem == NULL ) { + textPtr->undo = 1; + return TCL_ERROR; + } + + if ( ( elem != NULL ) && ( elem->type == SEPARATOR ) ) { + ckfree((char *) elem); + elem = popStack(&(textPtr->redoStack)); + } + + while ( elem && (elem->type != SEPARATOR) ) { + switch ( elem->type ) { + case INSERT: + TkTextGetIndex(interp, textPtr, elem->index, &fromIndex); + InsertChars(textPtr, &fromIndex, elem->string); + TkTextIndexForwBytes(&fromIndex, (int) strlen(elem->string), + &toIndex); + TkTextPrintIndex(&toIndex, viewIndex); + break; + case DELETE: + TkTextGetIndex(interp, textPtr, elem->index, &toIndex); + strcpy(viewIndex, elem->index); + TkTextIndexForwBytes(&toIndex, (int) strlen(elem->string), + &toIndex); + TkTextPrintIndex(&toIndex, buffer); + DeleteChars(textPtr, elem->index, buffer); + break; + default: + return TCL_ERROR; + } + pushStack(&(textPtr->undoStack), elem); + elem = popStack(&(textPtr->redoStack)); + } + + /* view the last changed position */ + + TkTextGetIndex(interp, textPtr, viewIndex, &toIndex); + TkTextSetMark(textPtr, "insert", &toIndex); + + /* insert a separator on the undo stack */ + + insertSeparator(&(textPtr->undoStack)); + + /* Turn back on the undo feature */ + + textPtr->undo = 1; + + return TCL_OK; +} + +/* + * TextEditCmd -- + * + * Handle the subcommands to "$text edit ...". + * See documentation for details. + * + * Results: + * None + * + * Side effects: + * None. + */ + +static int +TextEditCmd(textPtr, interp, argc, argv) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int argc; /* Number of arguments. */ + char **argv; /* Argument strings. */ +{ + int c, setModified; + size_t length; + + if (argc < 3) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " edit option ?arg arg ...?\"", (char *) NULL); + return TCL_ERROR; + } + c = argv[2][0]; + length = strlen(argv[2]); + if ((c == 'm') && (strncmp(argv[2], "modified", length) == 0)) { + if (argc == 3) { + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(textPtr->isDirty)); + } else if (argc != 4) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " edit modified ?boolean?\"", (char *) NULL); + return TCL_ERROR; + } else { + XEvent event; + if (Tcl_GetBoolean(interp, argv[3], &setModified) != TCL_OK) { + return TCL_ERROR; + } + /* + * Set or reset the dirty info and trigger a Modified event. + */ + if (setModified) { + textPtr->isDirty = 1; + textPtr->modifiedSet = 1; + } else { + textPtr->isDirty = 0; + textPtr->modifiedSet = 0; + } + + /* + * Send an event that the text was modified. This is equivalent to + * "event generate $textWidget <<Modified>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Modified"); + Tk_HandleEvent(&event); + } + } else if ((c == 'r') && (strncmp(argv[2], "redo", length) == 0) + && (length >= 3)) { + if (argc != 3) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " edit redo\"", (char *) NULL); + return TCL_ERROR; + } + if ( TextEditRedo(interp,textPtr) ) { + Tcl_AppendResult(interp, "nothing to redo", (char *) NULL); + return TCL_ERROR; + } + } else if ((c == 'r') && (strncmp(argv[2], "reset", length) == 0) + && (length >= 3)) { + if (argc != 3) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " edit reset\"", (char *) NULL); + return TCL_ERROR; + } + clearStack(&(textPtr->undoStack)); + clearStack(&(textPtr->redoStack)); + } else if ((c == 's') && (strncmp(argv[2], "separator", length) == 0)) { + if (argc != 3) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " edit separator\"", (char *) NULL); + return TCL_ERROR; + } + insertSeparator(&(textPtr->undoStack)); + } else if ((c == 'u') && (strncmp(argv[2], "undo", length) == 0)) { + if (argc != 3) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + argv[0], " edit undo\"", (char *) NULL); + return TCL_ERROR; + } + if ( TextEditUndo(interp,textPtr) ) { + Tcl_AppendResult(interp, "nothing to undo", + (char *) NULL); + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "bad edit option \"", argv[2], + "\": must be modified, redo, reset, separator or undo", + (char *) NULL); + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + * TextGetText -- + * returns the text from indexPtr1 to indexPtr2. + * + * Results: + * the string between indices indexPtr1 and indexPtr2 + * + * Side effects: + * None. + */ + +char * TextGetText(indexPtr1,indexPtr2) + TkTextIndex * indexPtr1; + TkTextIndex * indexPtr2; +{ + TkTextIndex tmpIndex; + char * string; + string = (char *) ckalloc(1); + *string = '\0'; + + TkTextMakeByteIndex(indexPtr1->tree, TkBTreeLineIndex(indexPtr1->linePtr), + indexPtr1->byteIndex, &tmpIndex); + + if (TkTextIndexCmp(indexPtr1, indexPtr2) >= 0) { + return string; + } + while (1) { + int offset, last, savedChar; + TkTextSegment *segPtr; + + segPtr = TkTextIndexToSeg(&tmpIndex, &offset); + last = segPtr->size; + if (tmpIndex.linePtr == indexPtr2->linePtr) { + int last2; + + if (indexPtr2->byteIndex == tmpIndex.byteIndex) { + break; + } + last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + offset; + if (last2 < last) { + last = last2; + } + } + if (segPtr->typePtr == &tkTextCharType) { + savedChar = segPtr->body.chars[last]; + segPtr->body.chars[last] = 0; + string = (char *) ckrealloc(string,strlen(string) + + strlen(segPtr->body.chars + offset) + 1); + strcat(string,segPtr->body.chars + offset); + segPtr->body.chars[last] = savedChar; + } + TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex); + } + + return string; +} + +/* + * updateDirtyFlag -- + * increases the dirtyness of the text widget + * + * Results: + * None + * + * Side effects: + * None. + */ + +static void updateDirtyFlag (textPtr) + TkText *textPtr; /* Information about text widget. */ +{ + int oldDirtyFlag; + + if (textPtr->modifiedSet) { + return; + } + oldDirtyFlag = textPtr->isDirty; + textPtr->isDirty += textPtr->isDirtyIncrement; + if (textPtr->isDirty == 0 || oldDirtyFlag == 0) { + XEvent event; + /* + * Send an event that the text was modified. This is equivalent to + * "event generate $textWidget <<Modified>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Modified"); + Tk_HandleEvent(&event); + } +} diff --git a/generic/tkText.h b/generic/tkText.h index f89c18f..b9e4727 100644 --- a/generic/tkText.h +++ b/generic/tkText.h @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.h,v 1.7 2000/01/06 02:18:58 hobbs Exp $ + * RCS: @(#) $Id: tkText.h,v 1.8 2001/11/13 00:19:05 hobbs Exp $ */ #ifndef _TKTEXT @@ -451,6 +451,25 @@ typedef struct TkTextTabArray { * BE THE LAST IN THE STRUCTURE. */ } TkTextTabArray; +/* enum definining the types used in an edit stack */ + +typedef enum { + SEPARATOR, /* Marker */ + INSERT, /* The undo is an insert */ + DELETE /* The undo is a delete */ +} TkTextEditType; + +/* strcut defining the basic undo/redo stack element */ + +typedef struct TkTextEditAtom { + TkTextEditType type; /* The type that will trigger the + * required action*/ + char * index; /* The starting index of the range */ + char * string; /* The text to be inserted / deleted */ + struct TkTextEditAtom * next; /* Pointer to the next element in the + * stack */ +} TkTextEditAtom; + /* * A data structure of the following type is kept for each text widget that * currently exists for this process: @@ -604,7 +623,7 @@ typedef struct TkText { /* Pointer to segment for "current" mark, * or NULL if none. */ XEvent pickEvent; /* The event from which the current character - * was chosen. Must be saved so that we + * was chosen. Must be saved so that we * can repick after modifications to the * text. */ int numCurTags; /* Number of tags associated with character @@ -616,15 +635,43 @@ typedef struct TkText { * Miscellaneous additional information: */ - char *takeFocus; /* Value of -takeFocus option; not used in + char *takeFocus; /* Value of -takeFocus option; not used in * the C code, but used by keyboard traversal * scripts. Malloc'ed, but may be NULL. */ char *xScrollCmd; /* Prefix of command to issue to update * horizontal scrollbar when view changes. */ char *yScrollCmd; /* Prefix of command to issue to update * vertical scrollbar when view changes. */ - int flags; /* Miscellaneous flags; see below for + int flags; /* Miscellaneous flags; see below for * definitions. */ + + /* + * Information related to the undo/redo functonality + */ + + TkTextEditAtom * undoStack; /* The undo stack */ + + TkTextEditAtom * redoStack; /* The redo stack */ + + int undo; /* non zero means the undo/redo behaviour is + * enabled */ + + int autoSeparators; /* non zero means the separatorss will be + * inserted automatically */ + + int modifiedSet; /* Flag indicating that the 'dirtynesss' of + * the text widget has been expplicitly set. + */ + + int isDirty; /* Flag indicating the 'dirtynesss' of the text + * widget. If the flag is not zero, unsaved + * modifications have been applied to the + * text widget */ + + int isDirtyIncrement; /* Amount with which the isDirty flag is + * incremented every edit action + */ + } TkText; /* diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c index c126fcc..689de37 100644 --- a/generic/tkTextTag.c +++ b/generic/tkTextTag.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextTag.c,v 1.5 2000/01/06 02:18:59 hobbs Exp $ + * RCS: @(#) $Id: tkTextTag.c,v 1.6 2001/11/13 00:19:05 hobbs Exp $ */ #include "default.h" @@ -151,7 +151,7 @@ TkTextTagCmd(textPtr, interp, argc, argv) index2 = index1; TkTextIndexForwChars(&index2, 1, &index2); } - + if (tagPtr->affectsDisplay) { TkTextRedrawTag(textPtr, &index1, &index2, tagPtr, !addTag); } else { @@ -159,18 +159,34 @@ TkTextTagCmd(textPtr, interp, argc, argv) * Still need to trigger enter/leave events on tags that * have changed. */ - + TkTextEventuallyRepick(textPtr); } TkBTreeTag(&index1, &index2, tagPtr, addTag); - + /* * If the tag is "sel" then grab the selection if we're supposed * to export it and don't already have it. Also, invalidate * partially-completed selection retrievals. */ - + if (tagPtr == textPtr->selTagPtr) { + XEvent event; + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + if (addTag && textPtr->exportSelection && !(textPtr->flags & GOT_SELECTION)) { Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, @@ -462,6 +478,25 @@ TkTextTagCmd(textPtr, interp, argc, argv) TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &last), TkBTreeTag(&first, &last, tagPtr, 0); + + if (tagPtr == textPtr->selTagPtr) { + XEvent event; + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + } + Tcl_DeleteHashEntry(hPtr); if (textPtr->bindingTable != NULL) { Tk_DeleteAllBindings(textPtr->bindingTable, |