summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
authorhobbs <hobbs>2001-11-13 00:19:05 (GMT)
committerhobbs <hobbs>2001-11-13 00:19:05 (GMT)
commite3690695eaccf83b8076fd877d17ed591c43065d (patch)
tree65c1489fbcc7a86e2140be2d55b1ffd0ccbd2577 /generic
parent3027f28d1f2411602324f0964f7f1ad28742cf93 (diff)
downloadtk-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.c663
-rw-r--r--generic/tkText.h55
-rw-r--r--generic/tkTextTag.c45
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,