Tk Source Code

Check-in [85fe3473]
Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merged TIP #446 accepted by TCT vote. This fixes [1273358] - Ask a text widget for the depth of its undo and redo stacks.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | core-8-6-branch
Files: files | file ages | folders
SHA1: 85fe3473de85857f46d00865c0f525c2b6b4e76c
User & Date: fvogel 2016-05-30 18:05:24
Original Comment: Merged TIP #446 accepted by TCT vote. This fixes [
Context
2016-05-30
18:14
Fixed [f2655bb0ec] - bind-15.23 fails if the mouse is inside the test window check-in: 2fc6c6a8 user: fvogel tags: core-8-6-branch
18:07
Merged TIP #446 accepted by TCT vote. This fixes [1273358] - Ask a text widget for the depth of its undo and redo stacks. check-in: 18b5c257 user: fvogel tags: trunk
18:05
Merged TIP #446 accepted by TCT vote. This fixes [1273358] - Ask a text widget for the depth of its undo and redo stacks. check-in: 85fe3473 user: fvogel tags: core-8-6-branch
2016-05-25
19:35
Fixed [109865fa01] - ttk::menubutton unsafe wrt widget destruction check-in: 392d70f3 user: fvogel tags: core-8-6-branch
2016-05-17
20:00
Took comments from Koen Danckaert into account (with my thanks) to simplify the implementation of TkUndoCanUndo() and TkUndoCanRedo() in tkUndo.c, and in tkText.c to remove direct calls to internals of the undo or redo stack Closed-Leaf check-in: b699eb0e user: fvogel tags: tip-446
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to doc/event.n.

347
348
349
350
351
352
353




354
355
356
357
358
359
360
action.
.TP
\fB<<TraverseOut>>\fR
This is sent to a widget when the focus leaves the widget because of a
user-driven
.QW "tab to widget"
action.




.TP
\fB<<WidgetViewSync>>\fR
This is sent to a text widget when its internal data become obsolete,
and again when these internal data are back in sync with the widget
view. The detail field (%d substitution) is either true (when the
widget is in sync) or false (when it is not).
.PP






>
>
>
>







347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
action.
.TP
\fB<<TraverseOut>>\fR
This is sent to a widget when the focus leaves the widget because of a
user-driven
.QW "tab to widget"
action.
.TP
\fB<<UndoStack>>\fR
This is sent to a text widget when its undo stack or redo stack becomes
empty or unempty.
.TP
\fB<<WidgetViewSync>>\fR
This is sent to a text widget when its internal data become obsolete,
and again when these internal data are back in sync with the widget
view. The detail field (%d substitution) is either true (when the
widget is in sync) or false (when it is not).
.PP

Changes to doc/text.n.

892
893
894
895
896
897
898



899
900
901
902
903
904
905
....
1278
1279
1280
1281
1282
1283
1284










1285
1286
1287
1288
1289
1290
1291
control.
.PP
Separators are inserted automatically when the \fB\-autoseparators\fR widget
option is true. You can insert separators programmatically as well. If a
separator is already present at the top of the undo stack no other will be
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 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
................................................................................
.
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 \fR?\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






>
>
>







 







>
>
>
>
>
>
>
>
>
>







892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
....
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
control.
.PP
Separators are inserted automatically when the \fB\-autoseparators\fR widget
option is true. You can insert separators programmatically as well. If a
separator is already present at the top of the undo stack no other will be
inserted. That means that two separators on the undo stack are always
separated by at least one insert or delete action.
.PP
The \fB<<UndoStack>>\fR virtual event is generated every time the undo stack
or the redo stack becomes empty or unempty.
.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 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
................................................................................
.
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 canredo\fR
.
Returns a boolean true if redo is possible, i.e. when the redo stack is not
empty. Otherwise returns false.
.TP
\fIpathName \fBedit canundo\fR
.
Returns a boolean true if undo is possible, i.e. when the undo stack is not
empty. Otherwise returns false.
.TP
\fIpathName \fBedit modified \fR?\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

Changes to generic/tkText.c.

397
398
399
400
401
402
403

404
405
406
407
408
409
410
....
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
....
2765
2766
2767
2768
2769
2770
2771

2772
2773
2774
2775
2776
2777
2778
....
2851
2852
2853
2854
2855
2856
2857



2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868




2869
2870
2871
2872
2873
2874
2875
....
5151
5152
5153
5154
5155
5156
5157



5158
5159

5160
5161

5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175




















5176
5177
5178
5179
5180
5181
5182
....
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219

5220
5221
5222
5223
5224




5225
5226
5227
5228
5229
5230


5231



5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244

5245
5246
5247
5248
5249




5250
5251
5252
5253
5254
5255
5256
....
5337
5338
5339
5340
5341
5342
5343

5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357


5358
5359































5360
5361
5362
5363
5364
5365
5366
....
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
....
5407
5408
5409
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
			    int what);
static int		TextEditUndo(TkText *textPtr);
static int		TextEditRedo(TkText *textPtr);
static Tcl_Obj *	TextGetText(const TkText *textPtr,
			    const TkTextIndex *index1,
			    const TkTextIndex *index2, int visibleOnly);
static void		GenerateModifiedEvent(TkText *textPtr);

static void		UpdateDirtyFlag(TkSharedText *sharedPtr);
static void		RunAfterSyncCmd(ClientData clientData);
static void		TextPushUndoAction(TkText *textPtr,
			    Tcl_Obj *undoString, int insert,
			    const TkTextIndex *index1Ptr,
			    const TkTextIndex *index2Ptr);
static int		TextSearchIndexInLine(const SearchSpec *searchSpecPtr,
................................................................................
     * Copy down shared flags.
     */

    textPtr->sharedTextPtr->undo = textPtr->undo;
    textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo;
    textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators;

    TkUndoSetDepth(textPtr->sharedTextPtr->undoStack,
	    textPtr->sharedTextPtr->maxUndo);

    /*
     * A few other options also need special processing, such as parsing the
     * geometry and setting the background from a 3-D border.
     */

................................................................................
    int insert,			/* 1 if insert, else delete. */
    const TkTextIndex *index1Ptr,
				/* Index describing first location. */
    const TkTextIndex *index2Ptr)
				/* Index describing second location. */
{
    TkUndoSubAtom *iAtom, *dAtom;


    /*
     * Create the helpers.
     */

    Tcl_Obj *seeInsertObj = Tcl_NewObj();
    Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
................................................................................
    TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom);
    TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom);

    Tcl_DecrRefCount(seeInsertObj);
    Tcl_DecrRefCount(index1Obj);
    Tcl_DecrRefCount(index2Obj);




    /*
     * Depending whether the action is to insert or delete, we provide the
     * appropriate second and third arguments to TkUndoPushAction. (The first
     * is the 'actionCommand', and the second the 'revertCommand').
     */

    if (insert) {
	TkUndoPushAction(textPtr->sharedTextPtr->undoStack, iAtom, dAtom);
    } else {
	TkUndoPushAction(textPtr->sharedTextPtr->undoStack, dAtom, iAtom);
    }




}
 
/*
 *----------------------------------------------------------------------
 *
 * TextUndoRedoCallback --
 *
................................................................................
TextEditCmd(
    TkText *textPtr,		/* Information about text widget. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int index, setModified, oldModified;



    static const char *const editOptionStrings[] = {
	"modified", "redo", "reset", "separator", "undo", NULL

    };
    enum editOptions {

	EDIT_MODIFIED, EDIT_REDO, EDIT_RESET, EDIT_SEPARATOR, EDIT_UNDO
    };

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObjStruct(interp, objv[2], editOptionStrings,
	    sizeof(char *), "edit option", 0, &index) != TCL_OK) {
	return TCL_ERROR;
    }

    switch ((enum editOptions) index) {




















    case EDIT_MODIFIED:
	if (objc == 3) {
	    Tcl_SetObjResult(interp,
		    Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty));
	    return TCL_OK;
	} else if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
................................................................................

	/*
	 * Only issue the <<Modified>> event if the flag actually changed.
	 * However, degree of modified-ness doesn't matter. [Bug 1799782]
	 */

	if ((!oldModified) != (!setModified)) {
            for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
                    textPtr = textPtr->next) {
                GenerateModifiedEvent(textPtr);
            }
	}
	break;
    case EDIT_REDO:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}

	if (TextEditRedo(textPtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
	    Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
	    return TCL_ERROR;
	}




	break;
    case EDIT_RESET:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}


	TkUndoClearStacks(textPtr->sharedTextPtr->undoStack);



	break;
    case EDIT_SEPARATOR:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
	break;
    case EDIT_UNDO:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}

	if (TextEditUndo(textPtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
	    Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
	    return TCL_ERROR;
	}




	break;
    }
    return TCL_OK;
}
 
/*
 *----------------------------------------------------------------------
................................................................................
/*
 *----------------------------------------------------------------------
 *
 * GenerateModifiedEvent --
 *
 *	Send an event that the text was modified. This is equivalent to:
 *	   event generate $textWidget <<Modified>>

 *
 * Results:
 *	None
 *
 * Side effects:
 *	May force the text window into existence.
 *
 *----------------------------------------------------------------------
 */

static void
GenerateModifiedEvent(
    TkText *textPtr)	/* Information about text widget. */
{


    Tk_MakeWindowExist(textPtr->tkwin);
    TkSendVirtualEvent(textPtr->tkwin, "Modified", NULL);































}
 
/*
 *----------------------------------------------------------------------
 *
 * UpdateDirtyFlag --
 *
................................................................................
 */

static void
UpdateDirtyFlag(
    TkSharedText *sharedTextPtr)/* Information about text widget. */
{
    int oldDirtyFlag;
    TkText *textPtr;

    /*
     * If we've been forced to be dirty, we stay dirty (until explicitly
     * reset, of course).
     */

    if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) {
................................................................................
    if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_UNDO) {
	sharedTextPtr->isDirty--;
    } else {
	sharedTextPtr->isDirty++;
    }

    if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) {
	for (textPtr = sharedTextPtr->peers; textPtr != NULL;
		textPtr = textPtr->next) {
	    GenerateModifiedEvent(textPtr);
	}
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * RunAfterSyncCmd --






>







 







|







 







>







 







>
>
>











>
>
>
>







 







>
>
>

|
>


>
|













>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
|
<



|



>
|




>
>
>
>






>
>

>
>
>









|



>





>
>
>
>







 







>
|













>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<







 







<
<
|
<







397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
....
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
....
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
....
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
....
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
....
5236
5237
5238
5239
5240
5241
5242


5243

5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
....
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
5393
5394
5395
5396
5397
5398
5399
5400
5401
5402
5403
5404
5405
5406
5407
5408
5409
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
5425
5426
5427
5428
5429
5430
5431
5432
5433
5434
5435
5436
5437
5438
5439
5440
5441
5442
5443
5444
5445
5446
....
5456
5457
5458
5459
5460
5461
5462

5463
5464
5465
5466
5467
5468
5469
....
5486
5487
5488
5489
5490
5491
5492


5493

5494
5495
5496
5497
5498
5499
5500
			    int what);
static int		TextEditUndo(TkText *textPtr);
static int		TextEditRedo(TkText *textPtr);
static Tcl_Obj *	TextGetText(const TkText *textPtr,
			    const TkTextIndex *index1,
			    const TkTextIndex *index2, int visibleOnly);
static void		GenerateModifiedEvent(TkText *textPtr);
static void		GenerateUndoStackEvent(TkText *textPtr);
static void		UpdateDirtyFlag(TkSharedText *sharedPtr);
static void		RunAfterSyncCmd(ClientData clientData);
static void		TextPushUndoAction(TkText *textPtr,
			    Tcl_Obj *undoString, int insert,
			    const TkTextIndex *index1Ptr,
			    const TkTextIndex *index2Ptr);
static int		TextSearchIndexInLine(const SearchSpec *searchSpecPtr,
................................................................................
     * Copy down shared flags.
     */

    textPtr->sharedTextPtr->undo = textPtr->undo;
    textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo;
    textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators;

    TkUndoSetMaxDepth(textPtr->sharedTextPtr->undoStack,
	    textPtr->sharedTextPtr->maxUndo);

    /*
     * A few other options also need special processing, such as parsing the
     * geometry and setting the background from a 3-D border.
     */

................................................................................
    int insert,			/* 1 if insert, else delete. */
    const TkTextIndex *index1Ptr,
				/* Index describing first location. */
    const TkTextIndex *index2Ptr)
				/* Index describing second location. */
{
    TkUndoSubAtom *iAtom, *dAtom;
    int canUndo, canRedo;

    /*
     * Create the helpers.
     */

    Tcl_Obj *seeInsertObj = Tcl_NewObj();
    Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
................................................................................
    TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom);
    TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom);

    Tcl_DecrRefCount(seeInsertObj);
    Tcl_DecrRefCount(index1Obj);
    Tcl_DecrRefCount(index2Obj);

    canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
    canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);

    /*
     * Depending whether the action is to insert or delete, we provide the
     * appropriate second and third arguments to TkUndoPushAction. (The first
     * is the 'actionCommand', and the second the 'revertCommand').
     */

    if (insert) {
	TkUndoPushAction(textPtr->sharedTextPtr->undoStack, iAtom, dAtom);
    } else {
	TkUndoPushAction(textPtr->sharedTextPtr->undoStack, dAtom, iAtom);
    }

    if (!canUndo || canRedo) {
        GenerateUndoStackEvent(textPtr);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * TextUndoRedoCallback --
 *
................................................................................
TextEditCmd(
    TkText *textPtr,		/* Information about text widget. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int index, setModified, oldModified;
    int canRedo = 0;
    int canUndo = 0;

    static const char *const editOptionStrings[] = {
	"canundo", "canredo", "modified", "redo", "reset", "separator",
        "undo", NULL
    };
    enum editOptions {
	EDIT_CANUNDO, EDIT_CANREDO, EDIT_MODIFIED, EDIT_REDO, EDIT_RESET,
	EDIT_SEPARATOR, EDIT_UNDO
    };

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObjStruct(interp, objv[2], editOptionStrings,
	    sizeof(char *), "edit option", 0, &index) != TCL_OK) {
	return TCL_ERROR;
    }

    switch ((enum editOptions) index) {
    case EDIT_CANREDO:
        if (objc != 3) {
            Tcl_WrongNumArgs(interp, 3, objv, NULL);
             return TCL_ERROR;
        }
        if (textPtr->sharedTextPtr->undo) {
            canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
        }
        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canRedo));
        break;
    case EDIT_CANUNDO:
        if (objc != 3) {
            Tcl_WrongNumArgs(interp, 3, objv, NULL);
             return TCL_ERROR;
        }
        if (textPtr->sharedTextPtr->undo) {
            canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
        }
        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canUndo));
        break;
    case EDIT_MODIFIED:
	if (objc == 3) {
	    Tcl_SetObjResult(interp,
		    Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty));
	    return TCL_OK;
	} else if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
................................................................................

	/*
	 * Only issue the <<Modified>> event if the flag actually changed.
	 * However, degree of modified-ness doesn't matter. [Bug 1799782]
	 */

	if ((!oldModified) != (!setModified)) {


            GenerateModifiedEvent(textPtr);

	}
	break;
    case EDIT_REDO:
        if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
        if (TextEditRedo(textPtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
	    Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
	    return TCL_ERROR;
	}
        canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
        if (!canUndo || !canRedo) {
            GenerateUndoStackEvent(textPtr);
        }
	break;
    case EDIT_RESET:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
        canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
        canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
	TkUndoClearStacks(textPtr->sharedTextPtr->undoStack);
        if (canUndo || canRedo) {
            GenerateUndoStackEvent(textPtr);
        }
	break;
    case EDIT_SEPARATOR:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
	TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
	break;
    case EDIT_UNDO:
        if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
	    return TCL_ERROR;
	}
        canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
	if (TextEditUndo(textPtr)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
	    Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
	    return TCL_ERROR;
	}
        canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
        if (!canRedo || !canUndo) {
            GenerateUndoStackEvent(textPtr);
        }
	break;
    }
    return TCL_OK;
}
 
/*
 *----------------------------------------------------------------------
................................................................................
/*
 *----------------------------------------------------------------------
 *
 * GenerateModifiedEvent --
 *
 *	Send an event that the text was modified. This is equivalent to:
 *	   event generate $textWidget <<Modified>>
  *	for all peers of $textWidget.
*
 * Results:
 *	None
 *
 * Side effects:
 *	May force the text window into existence.
 *
 *----------------------------------------------------------------------
 */

static void
GenerateModifiedEvent(
    TkText *textPtr)	/* Information about text widget. */
{
    for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
            textPtr = textPtr->next) {
        Tk_MakeWindowExist(textPtr->tkwin);
        TkSendVirtualEvent(textPtr->tkwin, "Modified", NULL);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * GenerateUndoStackEvent --
 *
 *	Send an event that the undo or redo stack became empty or unempty.
 *	This is equivalent to:
 *	   event generate $textWidget <<UndoStack>>
 *	for all peers of $textWidget.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	May force the text window (and all peers) into existence.
 *
 *----------------------------------------------------------------------
 */

static void
GenerateUndoStackEvent(
    TkText *textPtr)	/* Information about text widget. */
{
    for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
            textPtr = textPtr->next) {
        Tk_MakeWindowExist(textPtr->tkwin);
        TkSendVirtualEvent(textPtr->tkwin, "UndoStack", NULL);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * UpdateDirtyFlag --
 *
................................................................................
 */

static void
UpdateDirtyFlag(
    TkSharedText *sharedTextPtr)/* Information about text widget. */
{
    int oldDirtyFlag;


    /*
     * If we've been forced to be dirty, we stay dirty (until explicitly
     * reset, of course).
     */

    if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) {
................................................................................
    if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_UNDO) {
	sharedTextPtr->isDirty--;
    } else {
	sharedTextPtr->isDirty++;
    }

    if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) {


        GenerateModifiedEvent(sharedTextPtr->peers);

    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * RunAfterSyncCmd --

Changes to generic/tkUndo.c.

349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
...
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
474
475
476
477
478
479
480














































481
482
483
484
485
486
487
...
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
    stack->depth = 0;
    return stack;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoSetDepth --
 *
 *	Set the maximum depth of stack.
 *
 * Results:
 *	None.
 *
 * Side effects:
................................................................................
 *	May delete elements from the stack if the new maximum depth is smaller
 *	than the number of elements previously in the stack.
 *
 *----------------------------------------------------------------------
 */

void
TkUndoSetDepth(
    TkUndoRedoStack *stack,	/* An Undo/Redo stack */
    int maxdepth)		/* The maximum stack depth */
{
    stack->maxdepth = maxdepth;

    if (stack->maxdepth>0 && stack->depth>stack->maxdepth) {
	TkUndoAtom *elem, *prevelem;
................................................................................
    TkUndoClearStacks(stack);
    ckfree(stack);
}
 
/*
 *----------------------------------------------------------------------
 *














































 * TkUndoInsertUndoSeparator --
 *
 *	Insert a separator on the undo stack, indicating a border for an
 *	undo/redo chunk.
 *
 * Results:
 *	None.
................................................................................

void
TkUndoInsertUndoSeparator(
    TkUndoRedoStack *stack)
{
    if (TkUndoInsertSeparator(&stack->undoStack)) {
	stack->depth++;
	TkUndoSetDepth(stack, stack->maxdepth);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoRevert --






|







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
...
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
...
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
    stack->depth = 0;
    return stack;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoSetMaxDepth --
 *
 *	Set the maximum depth of stack.
 *
 * Results:
 *	None.
 *
 * Side effects:
................................................................................
 *	May delete elements from the stack if the new maximum depth is smaller
 *	than the number of elements previously in the stack.
 *
 *----------------------------------------------------------------------
 */

void
TkUndoSetMaxDepth(
    TkUndoRedoStack *stack,	/* An Undo/Redo stack */
    int maxdepth)		/* The maximum stack depth */
{
    stack->maxdepth = maxdepth;

    if (stack->maxdepth>0 && stack->depth>stack->maxdepth) {
	TkUndoAtom *elem, *prevelem;
................................................................................
    TkUndoClearStacks(stack);
    ckfree(stack);
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoCanRedo --
 *
 *	Returns true if redo is possible, i.e. if the redo stack is not empty.
 *
 * Results:
 *	 A boolean.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkUndoCanRedo(
    TkUndoRedoStack *stack)	/* An Undo/Redo stack */
{
    return stack->redoStack != NULL;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoCanUndo --
 *
 *	Returns true if undo is possible, i.e. if the undo stack is not empty.
 *
 * Results:
 *	 A boolean.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TkUndoCanUndo(
    TkUndoRedoStack *stack)	/* An Undo/Redo stack */
{
    return stack->undoStack != NULL;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoInsertUndoSeparator --
 *
 *	Insert a separator on the undo stack, indicating a border for an
 *	undo/redo chunk.
 *
 * Results:
 *	None.
................................................................................

void
TkUndoInsertUndoSeparator(
    TkUndoRedoStack *stack)
{
    if (TkUndoInsertSeparator(&stack->undoStack)) {
	stack->depth++;
	TkUndoSetMaxDepth(stack, stack->maxdepth);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * TkUndoRevert --

Changes to generic/tkUndo.h.

92
93
94
95
96
97
98
99
100
101


102
103
104
105
106
107
108
109
110
111
112
113
MODULE_SCOPE void	TkUndoClearStack(TkUndoAtom **stack);

/*
 * Functions for working on an undo/redo stack.
 */

MODULE_SCOPE TkUndoRedoStack *TkUndoInitStack(Tcl_Interp *interp, int maxdepth);
MODULE_SCOPE void	TkUndoSetDepth(TkUndoRedoStack *stack, int maxdepth);
MODULE_SCOPE void	TkUndoClearStacks(TkUndoRedoStack *stack);
MODULE_SCOPE void	TkUndoFreeStack(TkUndoRedoStack *stack);


MODULE_SCOPE void	TkUndoInsertUndoSeparator(TkUndoRedoStack *stack);
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeCmdSubAtom(Tcl_Command command,
			    Tcl_Obj *actionScript, TkUndoSubAtom *subAtomList);
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeSubAtom(TkUndoProc *funcPtr,
			    ClientData clientData, Tcl_Obj *actionScript,
			    TkUndoSubAtom *subAtomList);
MODULE_SCOPE void	TkUndoPushAction(TkUndoRedoStack *stack,
			    TkUndoSubAtom *apply, TkUndoSubAtom *revert);
MODULE_SCOPE int	TkUndoRevert(TkUndoRedoStack *stack);
MODULE_SCOPE int	TkUndoApply(TkUndoRedoStack *stack);

#endif /* _TKUNDO */






|


>
>












92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
MODULE_SCOPE void	TkUndoClearStack(TkUndoAtom **stack);

/*
 * Functions for working on an undo/redo stack.
 */

MODULE_SCOPE TkUndoRedoStack *TkUndoInitStack(Tcl_Interp *interp, int maxdepth);
MODULE_SCOPE void	TkUndoSetMaxDepth(TkUndoRedoStack *stack, int maxdepth);
MODULE_SCOPE void	TkUndoClearStacks(TkUndoRedoStack *stack);
MODULE_SCOPE void	TkUndoFreeStack(TkUndoRedoStack *stack);
MODULE_SCOPE int	TkUndoCanRedo(TkUndoRedoStack *stack);
MODULE_SCOPE int	TkUndoCanUndo(TkUndoRedoStack *stack);
MODULE_SCOPE void	TkUndoInsertUndoSeparator(TkUndoRedoStack *stack);
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeCmdSubAtom(Tcl_Command command,
			    Tcl_Obj *actionScript, TkUndoSubAtom *subAtomList);
MODULE_SCOPE TkUndoSubAtom *TkUndoMakeSubAtom(TkUndoProc *funcPtr,
			    ClientData clientData, Tcl_Obj *actionScript,
			    TkUndoSubAtom *subAtomList);
MODULE_SCOPE void	TkUndoPushAction(TkUndoRedoStack *stack,
			    TkUndoSubAtom *apply, TkUndoSubAtom *revert);
MODULE_SCOPE int	TkUndoRevert(TkUndoRedoStack *stack);
MODULE_SCOPE int	TkUndoApply(TkUndoRedoStack *stack);

#endif /* _TKUNDO */

Changes to tests/text.test.

6203
6204
6205
6206
6207
6208
6209
6210
6211
6212
6213
6214
6215
6216
6217
....
6490
6491
6492
6493
6494
6495
6496





































































6497
6498
6499
6500
6501
6502
6503
    destroy .t
} -returnCodes {error} -result {wrong # args: should be ".t edit option ?arg ...?"}
test text-27.2 {TextEditCmd procedure, argument parsing} -body {
    pack [text .t]
    .t edit gorp
} -cleanup {
    destroy .t
} -returnCodes {error} -result {bad edit option "gorp": must be modified, redo, reset, separator, or undo}
test text-27.3 {TextEditUndo procedure, undoing changes} -body {
    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
................................................................................
    event generate .top.t <<Cut>>
    event generate .top.t <Delete>
    event generate .top.t <<Undo>>
    .top.t get 1.0 "1.0 lineend"
} -cleanup {
    destroy .top.t .top
} -result "This A an example text"






































































test text-28.1 {bug fix - 624372, ControlUtfProc long lines} -body {
    pack [text .t -wrap none]
    .t insert end [string repeat "\1" 500]
} -cleanup {
    destroy .t
} -result {}






|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







6203
6204
6205
6206
6207
6208
6209
6210
6211
6212
6213
6214
6215
6216
6217
....
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
6504
6505
6506
6507
6508
6509
6510
6511
6512
6513
6514
6515
6516
6517
6518
6519
6520
6521
6522
6523
6524
6525
6526
6527
6528
6529
6530
6531
6532
6533
6534
6535
6536
6537
6538
6539
6540
6541
6542
6543
6544
6545
6546
6547
6548
6549
6550
6551
6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562
6563
6564
6565
6566
6567
6568
6569
6570
6571
6572
    destroy .t
} -returnCodes {error} -result {wrong # args: should be ".t edit option ?arg ...?"}
test text-27.2 {TextEditCmd procedure, argument parsing} -body {
    pack [text .t]
    .t edit gorp
} -cleanup {
    destroy .t
} -returnCodes {error} -result {bad edit option "gorp": must be canundo, canredo, modified, redo, reset, separator, or undo}
test text-27.3 {TextEditUndo procedure, undoing changes} -body {
    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
................................................................................
    event generate .top.t <<Cut>>
    event generate .top.t <Delete>
    event generate .top.t <<Undo>>
    .top.t get 1.0 "1.0 lineend"
} -cleanup {
    destroy .top.t .top
} -result "This A an example text"
test text-27.24 {TextEditCmd procedure, canundo and canredo} -setup {
    destroy .t
    set res {}
} -body {
    text .t -undo false -autoseparators false
    lappend res [.t edit canundo] [.t edit canredo]
    .t configure -undo true
    lappend res [.t edit canundo] [.t edit canredo]
    .t insert end "DO\n"
    .t edit separator
    .t insert end "IT\n"
    .t insert end "YOURSELF\n"
    .t edit separator
    lappend res [.t edit canundo] [.t edit canredo]
    .t edit undo
    lappend res [.t edit canundo] [.t edit canredo]
    .t configure -undo false
    lappend res [.t edit canundo] [.t edit canredo]
    .t configure -undo true
    lappend res [.t edit canundo] [.t edit canredo]
    .t edit redo
    lappend res [.t edit canundo] [.t edit canredo]
} -cleanup {
    destroy .t
} -result {0 0 0 0 1 0 1 1 0 0 1 1 1 0}
test text-27.25 {<<UndoStack>> virtual event} -setup {
    destroy .t
    set res {}
    set nbUS 0
} -body {
    text .t -undo false -autoseparators false
    bind .t <<UndoStack>> {incr nbUS}
    update ; lappend res $nbUS
    .t configure -undo true
    update ; lappend res $nbUS
    .t insert end "DO\n"
    .t edit separator
    .t insert end "IT\n"
    .t insert end "YOURSELF\n"
    .t edit separator
    .t insert end "MAN\n"
    .t edit separator
    update ; lappend res $nbUS
    .t edit undo
    update ; lappend res $nbUS
    .t edit redo
    update ; lappend res $nbUS
    .t edit undo
    update ; lappend res $nbUS
    .t edit undo
    update ; lappend res $nbUS
    .t edit undo
    update ; lappend res $nbUS
    .t edit redo
    update ; lappend res $nbUS
    .t edit redo
    update ; lappend res $nbUS
    .t edit redo
    update ; lappend res $nbUS
    .t edit undo
    update ; lappend res $nbUS
    .t edit undo
    update ; lappend res $nbUS
    .t edit reset
    update ; lappend res $nbUS
} -cleanup {
    destroy .t
} -result {0 0 1 2 3 4 4 5 6 6 7 8 8 9}


test text-28.1 {bug fix - 624372, ControlUtfProc long lines} -body {
    pack [text .t -wrap none]
    .t insert end [string repeat "\1" 500]
} -cleanup {
    destroy .t
} -result {}