From a7a15bdf1fb88c412c6d1556924d2c87a2d27a32 Mon Sep 17 00:00:00 2001 From: hobbs Date: Tue, 13 Nov 2001 00:19:05 +0000 Subject: added TIP#26 text widget undo/redo functionality --- ChangeLog | 15 ++ doc/text.n | 96 +++++++- generic/tkText.c | 663 +++++++++++++++++++++++++++++++++++++++++++++------ generic/tkText.h | 55 ++++- generic/tkTextTag.c | 45 +++- library/text.tcl | 53 +++- library/tk.tcl | 8 +- mac/tkMacDefault.h | 4 +- tests/text.test | 129 +++++++++- unix/tkUnixDefault.h | 4 +- win/tkWinDefault.h | 4 +- 11 files changed, 979 insertions(+), 97 deletions(-) diff --git a/ChangeLog b/ChangeLog index b369cd5..984284f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2001-11-12 Jeff Hobbs + + * doc/text.n: + * generic/tkText.c: + * generic/tkText.h: + * generic/tkTextTag.c: + * library/text.tcl: + * library/tk.tcl: + * mac/tkMacDefault.h: + * tests/text.test: + * unix/tkUnixDefault.h: + * win/tkWinDefault.h: added TIP #26 implementation of simple + built-in undo/redo of text editing in the text widget. + [Patch #458879] (callewaert) + 2001-11-12 Donal K. Fellows * library/demos/menu.tcl: Show off -compound support in menus. diff --git a/doc/text.n b/doc/text.n index bd67d57..df2b497 100644 --- a/doc/text.n +++ b/doc/text.n @@ -5,7 +5,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: text.n,v 1.9 2001/08/01 16:21:11 dgp Exp $ +'\" RCS: @(#) $Id: text.n,v 1.10 2001/11/13 00:19:05 hobbs Exp $ '\" .so man.macros .TH text n 4.0 Tk "Tk Built-In Commands" @@ -32,6 +32,9 @@ text, tk_textCopy, tk_textCut, tk_textPaste \- Create and manipulate text widget \-highlightcolor \-pady \-yscrollcommand .SE .SH "WIDGET-SPECIFIC OPTIONS" +.OP \-autoseparators autoSseparators AutoSeparators +Specifies whether separators are automatically inserted in the undo +stack. Is only active when the \fB\-undo\fR option is true. .OP \-height height Height Specifies the desired height for the window, in units of characters in the font given by the \fB\-font\fR option. @@ -89,6 +92,9 @@ options in tags. If no \fB\-tabs\fR option is specified, or if it is specified as an empty list, then Tk uses default tabs spaced every eight (average size) characters. +.OP \-undo undo Undo +Specifies whether the undo mechanism is active (value \fB0\fR) or +not (value \fB1\fR). .OP \-width width Width Specifies the desired width for the window in units of characters in the font given by the \fB\-font\fR option. @@ -147,6 +153,9 @@ The fourth form of annotation allows Tk images to be embedded in a text widget. See EMBEDDED IMAGES below for more details. .VE +.PP +The text widget also has a built-in undo/redo mechanism. See +UNDO MECHANISM below for more details. .SH INDICES .PP @@ -688,6 +697,9 @@ characters with the \fBsel\fR tag. If the selection is claimed away by another application or by another window within this application, then the \fBsel\fR tag will be removed from all characters in the text. +.IP [4] +Whenever the \fBsel\fR tag range changes a virtual event +\fB<>\fR is generated. .PP The \fBsel\fR tag is automatically defined when a text widget is created, and it may not be deleted with the ``\fIpathName \fBtag delete\fR'' @@ -708,6 +720,47 @@ The \fBinsert\fR mark represents the position of the insertion cursor, and the insertion cursor will automatically be drawn at this point whenever the text widget has the input focus. +.SH "THE MODIFIED FLAG" +The text widget can keep track of changes to the content of the widget +by means of the modified flag. Inserting or deleting text will set +this flag. The flag can be queried, set and cleared programatically +as well. Whenever the flag changes state a \fB<>\fR virtual +event is generated. See the \fBedit modified\fR widget command for +more details. + +.SH "THE UNDO MECHANISM" +.PP +The text widget has an unlimited undo and redo mechanism. This undo +mechanism is only active when the \fB-undo\fR widget option is true. +Every insert and delete action is recorded on this stack. +.PP +Boundaries are inserted between edit actions: the so-called +separators. The purpose of these separators is to group inserts and +deletes into one compound edit action. When undoing a change everything +between two separators will be undone. The undone changes are then +moved to the redo stack, so that an undone edit can be redone again. +The redo stack is cleared whenever new edit actions are recorded +on the undo stack. The undo and redo stacks can be cleared to keep +their depth under control. +.PP +Separators are inserted automatically when the +\fB-autoseparators\fR widget option is true. You can insert separators +programatically as well. If a separator is already present at the top +of the undo stack no other will inserted. That means that two +separators on the undo stack are always separated by at least one +insert or delete action. +.PP +The undo mechanism is also linked to the modified flag. This means +that undoing or redoing changes can take a modified text widget back +to the unmodified state or vice versa. The modified flag +will be set to automatically to the appropriate state. This +automatic coupling does not work when the modified flag has been +set by the user, until the flag has been reset again. + +.PP +See below for the \fBedit\fR widget command that controls the +undo mechanism. + .SH "WIDGET COMMAND" .PP The \fBtext\fR command creates a new Tcl command whose @@ -876,6 +929,41 @@ In this case an empty string is returned, and you must query the window by its index position to get more information. .RE .TP +\fIpathName \fBedit \fIoption \fR?\fIarg arg ...\fR? +This command controls the undo mechanism and the modified flag. +The exact behavior of the command depends on the \fIoption\fR +argument that follows the \fBedit\fR argument. The following +forms of the command are currently supported: +.RS +.TP +\fIpathName \fBedit modified ?\fIboolean\fR? +If \fIboolean\fR is not specified, returns the modified flag of +the widget. The insert, delete, edit undo and edit redo commands +or the user can set or clear the modified flag. +If \fIboolean\fR is specified, sets the modified flag of the widget +to \fIboolean\fR. +.TP +\fIpathName \fBedit redo\fR +When the \fB-undo\fR option is true, reapplies the last undone +edits provided no other edits were done since then. Generates an +error when the redo stack is empty. +Does nothing when the \fB-undo\fR option is false. +.TP +\fIpathName \fBedit reset\fR +Clears the undo and redo stacks. +.TP +\fIpathName \fBedit separator\fR +Inserts a separator (boundary) on the undo stack. Does nothing +when the \fB-undo\fR option is false. +.TP +\fIpathName \fBedit undo\fR +Undoes the last edit action when the \fB-undo\fR option is true. +An edit action is defined as all the insert and delete commands that are +recorded on the undo stack in between two separators. Generates an error +when the undo stack is empty. +Does nothing when the \fB-undo\fR option is false. +.RE +.TP \fIpathName \fBget \fIindex1 \fR?\fIindex2\fR? Return a range of characters from the text. The return value will be all the characters in the text starting @@ -1613,6 +1701,12 @@ Control-x deletes whatever is selected in the text widget. .IP [31] Control-t reverses the order of the two characters to the right of the insertion cursor. +.IP [32] +Control-z undoes the last edit action if the \fB-undo\fR option is +true. Does nothing otherwise. +.IP [33] +Control-Z reapplies the last undone edit action if the \fB-undo\fR +option is true. Does nothing otherwise. .PP If the widget is disabled using the \fB\-state\fR option, then its view can still be adjusted and text can still be selected, 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 <>" + */ + + 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 <>" + */ + + 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 <>" + */ + + 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 <>" + */ + + 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 <>" + */ + + 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, diff --git a/library/text.tcl b/library/text.tcl index 9abc9ae..c1a2b54 100644 --- a/library/text.tcl +++ b/library/text.tcl @@ -3,7 +3,7 @@ # This file defines the default bindings for Tk text widgets and provides # procedures that help in implementing the bindings. # -# RCS: @(#) $Id: text.tcl,v 1.17 2001/08/27 01:44:48 dgp Exp $ +# RCS: @(#) $Id: text.tcl,v 1.18 2001/11/13 00:19:05 hobbs Exp $ # # Copyright (c) 1992-1994 The Regents of the University of California. # Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -204,6 +204,7 @@ bind Text { } bind Text { tk::TextInsert %W \n + if {[%W cget -autoseparators]} {%W edit separator} } bind Text { if {[string compare [%W tag nextrange sel 1.0 end] ""]} { @@ -338,6 +339,18 @@ bind Text { } } +bind Text <> { + if { ! [ catch { %W edit undo } ] } { + %W see insert + } +} + +bind Text <> { + if { ! [ catch { %W edit redo } ] } { + %W see insert + } +} + if {[string compare $tcl_platform(platform) "windows"]} { bind Text { if {!$tk_strictMotif} { @@ -518,6 +531,7 @@ proc ::tk::TextButton1 {w x y} { $w mark set insert [TextClosestGap $w $x $y] $w mark set anchor insert if {[string equal [$w cget -state] "normal"]} {focus $w} + if {[$w cget -autoseparators]} {$w edit separator} } # ::tk::TextSelectTo -- @@ -628,6 +642,18 @@ proc ::tk::TextKeyExtend {w index} { proc ::tk::TextPaste {w x y} { $w mark set insert [TextClosestGap $w $x $y] catch {$w insert insert [::tk::GetSelection $w PRIMARY]} + catch { + set oldSeparator [$w cget -autoseparators] + if {$oldSeparator} { + $w configure -autoseparators 0 + $w edit separator + } + $w insert insert [::tk::GetSelection $w PRIMARY] + if {$oldSeparator} { + $w edit separator + $w configure -autoseparators 1 + } + } if {[string equal [$w cget -state] "normal"]} {focus $w} } @@ -678,6 +704,7 @@ proc ::tk::TextSetCursor {w pos} { $w mark set insert $pos $w tag remove sel 1.0 end $w see insert + if {[$w cget -autoseparators]} {$w edit separator} } # ::tk::TextKeySelect @@ -785,14 +812,25 @@ proc ::tk::TextInsert {w s} { if {[string equal $s ""] || [string equal [$w cget -state] "disabled"]} { return } + set compound 0 catch { if {[$w compare sel.first <= insert] \ && [$w compare sel.last >= insert]} { + set oldSeparator [$w cget -autoseparators] + if { $oldSeparator } { + $w configure -autoseparators 0 + $w edit separator + set compound 1 + } $w delete sel.first sel.last } } $w insert insert $s $w see insert + if { $compound && $oldSeparator } { + $w edit separator + $w configure -autoseparators 1 + } } # ::tk::TextUpDownLine -- @@ -966,12 +1004,19 @@ proc ::tk_textCut w { proc ::tk_textPaste w { global tcl_platform catch { + set oldSeparator [$w cget -autoseparators] + if { $oldSeparator } { + $w configure -autoseparators 0 + $w edit separator + } if {[string compare $tcl_platform(platform) "unix"]} { - catch { - $w delete sel.first sel.last - } + catch { $w delete sel.first sel.last } } $w insert insert [::tk::GetSelection $w CLIPBOARD] + if { $oldSeparator } { + $w edit separator + $w configure -autoseparators 1 + } } } diff --git a/library/tk.tcl b/library/tk.tcl index 102ac4c..f62379b 100644 --- a/library/tk.tcl +++ b/library/tk.tcl @@ -3,7 +3,7 @@ # Initialization script normally executed in the interpreter for each # Tk-based application. Arranges class bindings for widgets. # -# RCS: @(#) $Id: tk.tcl,v 1.31 2001/08/01 16:21:11 dgp Exp $ +# RCS: @(#) $Id: tk.tcl,v 1.32 2001/11/13 00:19:05 hobbs Exp $ # # Copyright (c) 1992-1994 The Regents of the University of California. # Copyright (c) 1994-1996 Sun Microsystems, Inc. @@ -313,6 +313,8 @@ switch $::tcl_platform(platform) { event add <> event add <> event add <> + event add <> + event add <> # Some OS's define a goofy (as in, not ) keysym # that is returned when the user presses . In order for # tab traversal to work, we have to add these keysyms to the @@ -337,6 +339,8 @@ switch $::tcl_platform(platform) { event add <> event add <> event add <> + event add <> + event add <> } "macintosh" { event add <> @@ -344,6 +348,8 @@ switch $::tcl_platform(platform) { event add <> event add <> event add <> + event add <> + event add <> } } # ---------------------------------------------------------------------- diff --git a/mac/tkMacDefault.h b/mac/tkMacDefault.h index 269bff2..0c78e71 100644 --- a/mac/tkMacDefault.h +++ b/mac/tkMacDefault.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: tkMacDefault.h,v 1.11 2001/10/12 13:30:31 tmh Exp $ + * RCS: @(#) $Id: tkMacDefault.h,v 1.12 2001/11/13 00:19:05 hobbs Exp $ */ #ifndef _TKMACDEFAULT @@ -433,6 +433,7 @@ * Defaults for texts: */ +#define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR NORMAL_BG #define DEF_TEXT_BG_MONO WHITE #define DEF_TEXT_BORDER_WIDTH "0" @@ -467,6 +468,7 @@ #define DEF_TEXT_STATE "normal" #define DEF_TEXT_TABS "" #define DEF_TEXT_TAKE_FOCUS (char *) NULL +#define DEF_TEXT_UNDO "0" #define DEF_TEXT_WIDTH "80" #define DEF_TEXT_WRAP "char" #define DEF_TEXT_XSCROLL_COMMAND "" diff --git a/tests/text.test b/tests/text.test index 94ba999..cddc199 100644 --- a/tests/text.test +++ b/tests/text.test @@ -6,7 +6,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: text.test,v 1.13 2001/08/01 16:21:12 dgp Exp $ +# RCS: @(#) $Id: text.test,v 1.14 2001/11/13 00:19:05 hobbs Exp $ if {[lsearch [namespace children] ::tcltest] == -1} { source [file join [pwd] [file dirname [info script]] defs.tcl] @@ -51,6 +51,7 @@ catch {destroy .t2} text .t2 set i 0 foreach test { + {-autoseparators yes 1 nah} {-background #ff00ff #ff00ff } {-bd 4 4 foo} {-bg blue blue #xx} @@ -83,6 +84,7 @@ foreach test { {-spacing3 -10 0 bogus} {-state d disabled foo} {-tabs {1i 2i 3i 4i} {1i 2i 3i 4i} bad_tabs} + {-undo 1 1 eh} {-width 73 73 2.4} {-wrap w word bad_wrap} } { @@ -111,7 +113,7 @@ test text-1.[incr i] {text options} { lappend result [lindex $i 4] } set result -} {blue {} {} 7 watch 0 {} fixed #012 5 #123 #234 0 green 45 100 47 2 3 82 raised #ffff01234567 21 yellow 0 0 0 0 disabled {1i 2i 3i 4i} {any old thing} 73 word {x scroll command} {test command}} +} {1 blue {} {} 7 watch 0 {} fixed #012 5 #123 #234 0 green 45 100 47 2 3 82 raised #ffff01234567 21 yellow 0 0 0 0 disabled {1i 2i 3i 4i} {any old thing} 1 73 word {x scroll command} {test command}} test text-2.1 {Tk_TextCmd procedure} { list [catch {text} msg] $msg @@ -150,7 +152,7 @@ test text-3.1 {TextWidgetCmd procedure, basics} { } {1 {wrong # args: should be ".t option ?arg arg ...?"}} test text-3.2 {TextWidgetCmd procedure} { list [catch {.t gorp 1.0 z 1.2} msg] $msg -} {1 {bad option "gorp": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} +} {1 {bad option "gorp": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} test text-4.1 {TextWidgetCmd procedure, "bbox" option} { list [catch {.t bbox} msg] $msg @@ -218,7 +220,7 @@ test text-6.13 {TextWidgetCmd procedure, "compare" option} { } {1 {bad comparison operator "z": must be <, <=, ==, >=, >, or !=}} test text-6.14 {TextWidgetCmd procedure, "compare" option} { list [catch {.t co 1.0 z 1.2} msg] $msg -} {1 {bad option "co": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} +} {1 {bad option "co": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} # "configure" option is already covered above @@ -227,7 +229,7 @@ test text-7.1 {TextWidgetCmd procedure, "debug" option} { } {1 {wrong # args: should be ".t debug boolean"}} test text-7.2 {TextWidgetCmd procedure, "debug" option} { list [catch {.t de 0 1} msg] $msg -} {1 {bad option "de": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} +} {1 {bad option "de": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} test text-7.3 {TextWidgetCmd procedure, "debug" option} { .t debug true .t deb @@ -310,7 +312,7 @@ test text-10.2 {TextWidgetCmd procedure, "index" option} { } {1 {wrong # args: should be ".t index index"}} test text-10.3 {TextWidgetCmd procedure, "index" option} { list [catch {.t in a b} msg] $msg -} {1 {bad option "in": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} +} {1 {bad option "in": must be bbox, cget, compare, configure, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, scan, search, see, tag, window, xview, or yview}} test text-10.4 {TextWidgetCmd procedure, "index" option} { list [catch {.t index @xyz} msg] $msg } {1 {bad text index "@xyz"}} @@ -369,7 +371,7 @@ test text-11.10 {TextWidgetCmd procedure, "insert" option} { list [.t get 1.0 1.end] [.t tag ranges bold] [.t tag ranges silly] } {{First second} {1.0 1.5} {1.5 1.12}} -# Mark, scan, search, see, tag, window, xview, and yview actions are tested elsewhere. +# Edit, mark, scan, search, see, tag, window, xview, and yview actions are tested elsewhere. test text-12.1 {ConfigureText procedure} { list [catch {.t2 configure -state foobar} msg] $msg @@ -1366,6 +1368,119 @@ test text-24.1 {bug fix - 1642} { .t search -backward -regexp "\$" insert 1.0 } {2.6} +test text-25.1 {TextEditCmd procedure, argument parsing} { + list [catch {.t edit} msg] $msg +} {1 {wrong # args: should be ".t edit option ?arg arg ...?"}} + +test text-25.2 {TextEditCmd procedure, argument parsing} { + list [catch {.t edit gorp} msg] $msg +} {1 {bad edit option "gorp": must be modified, redo, reset, separator or undo}} + +test text-25.3 {TextEditUndo procedure, undoing changes} { + catch {destroy .t} + text .t -undo 1 + pack .t + .t insert end "line 1\n" + .t delete 1.4 1.6 + .t insert end "should be gone after undo\n" + .t edit undo + .t get 1.0 end +} "line\n\n" + +test text-25.4 {TextEditRedo procedure, redoing changes} { + catch {destroy .t} + text .t -undo 1 + pack .t + .t insert end "line 1\n" + .t delete 1.4 1.6 + .t insert end "should be back after redo\n" + .t edit undo + .t edit redo + .t get 1.0 end +} "line\nshould be back after redo\n\n" + +test text-25.5 {TextEditUndo procedure, resetting stack} { + catch {destroy .t} + text .t -undo 1 + pack .t + .t insert end "line 1\n" + .t delete 1.4 1.6 + .t insert end "should be back after redo\n" + .t edit reset + catch {.t edit undo} msg + set msg +} "nothing to undo" + +test text-25.6 {TextEditCmd procedure, insert separator} { + catch {destroy .t} + text .t -undo 1 + pack .t + .t insert end "line 1\n" + .t edit separator + .t insert end "line 2\n" + .t edit undo + .t get 1.0 end +} "line 1\n\n" + +test text-25.7 {-autoseparators configuration option} { + catch {destroy .t} + text .t -undo 1 -autoseparators 0 + pack .t + .t insert end "line 1\n" + .t delete 1.4 1.6 + .t insert end "line 2\n" + .t edit undo + .t get 1.0 end +} "\n" + +test text-25.8 {TextEditCmd procedure, modified flag} { + catch {destroy .t} + text .t + pack .t + .t insert end "line 1\n" + .t edit modified +} {1} + +test text-25.9 {TextEditCmd procedure, reset modified flag} { + catch {destroy .t} + text .t + pack .t + .t insert end "line 1\n" + .t edit modified 0 + .t edit modified +} {0} + +test text-25.10 {TextEditCmd procedure, set modified flag} { + catch {destroy .t} + text .t + pack .t + .t edit modified 1 + .t edit modified +} {1} + +test text-25.11 {<> virtual event} { + set ::retval unmodified + catch {destroy .t} + text .t -undo 1 + pack .t + bind .t <> "set ::retval modified" + update idletasks + .t insert end "nothing special\n" + set ::retval +} {modified} + +test text-25.12 {<> virtual event} { + set ::retval no_selection + catch {destroy .t} + text .t -undo 1 + pack .t + bind .t <> "set ::retval selection_changed" + update idletasks + .t insert end "nothing special\n" + .t tag add sel 1.0 1.1 + set ::retval +} {selection_changed} + eval destroy [winfo child .] option clear diff --git a/unix/tkUnixDefault.h b/unix/tkUnixDefault.h index c58179c..76ae654 100644 --- a/unix/tkUnixDefault.h +++ b/unix/tkUnixDefault.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: tkUnixDefault.h,v 1.11 2001/10/12 13:30:31 tmh Exp $ + * RCS: @(#) $Id: tkUnixDefault.h,v 1.12 2001/11/13 00:19:05 hobbs Exp $ */ #ifndef _TKUNIXDEFAULT @@ -423,6 +423,7 @@ * Defaults for texts: */ +#define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR NORMAL_BG #define DEF_TEXT_BG_MONO WHITE #define DEF_TEXT_BORDER_WIDTH "2" @@ -457,6 +458,7 @@ #define DEF_TEXT_STATE "normal" #define DEF_TEXT_TABS "" #define DEF_TEXT_TAKE_FOCUS (char *) NULL +#define DEF_TEXT_UNDO "0" #define DEF_TEXT_WIDTH "80" #define DEF_TEXT_WRAP "char" #define DEF_TEXT_XSCROLL_COMMAND "" diff --git a/win/tkWinDefault.h b/win/tkWinDefault.h index d1d1286..8379b9c 100644 --- a/win/tkWinDefault.h +++ b/win/tkWinDefault.h @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkWinDefault.h,v 1.11 2001/10/12 13:30:32 tmh Exp $ + * RCS: @(#) $Id: tkWinDefault.h,v 1.12 2001/11/13 00:19:05 hobbs Exp $ */ #ifndef _TKWINDEFAULT @@ -428,6 +428,7 @@ * Defaults for texts: */ +#define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR "SystemWindow" #define DEF_TEXT_BG_MONO WHITE #define DEF_TEXT_BORDER_WIDTH "2" @@ -462,6 +463,7 @@ #define DEF_TEXT_STATE "normal" #define DEF_TEXT_TABS "" #define DEF_TEXT_TAKE_FOCUS (char *) NULL +#define DEF_TEXT_UNDO "0" #define DEF_TEXT_WIDTH "80" #define DEF_TEXT_WRAP "char" #define DEF_TEXT_XSCROLL_COMMAND "" -- cgit v0.12