Tk Source Code

Changes On Branch bug-43573999ca
Login

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

Changes In Branch bug-43573999ca Excluding Merge-Ins

This is equivalent to a diff from 87d8b0cd to df4a096e

2022-10-26
19:40
Fix [43573999ca]: Problem with tkBind.c since Tk 8.6.10 (various assertions fail). check-in: 4ad7dd2e user: fvogel tags: core-8-6-branch
2022-10-25
20:33
Fix [5e4e44a93b]: ttk::panedwindow chokes on drag. Closed-Leaf check-in: 347a8882 user: fvogel tags: bug-5e4e44a93b
19:51
Rework comments a bit more. Closed-Leaf check-in: df4a096e user: fvogel tags: bug-43573999ca
19:43
Rework comments a bit. check-in: 51b9372b user: fvogel tags: bug-43573999ca
2022-10-24
21:59
Rebase to latest 8.6 check-in: b6d0e36b user: jan.nijtmans tags: cygwin
05:49
Add test bind-37.1 demonstrating bug [43573999ca]. This test currently crashes (on all platforms). check-in: 802fde74 user: fvogel tags: bug-43573999ca
2022-10-18
20:15
merge 8.6 check-in: 1bcc6791 user: fvogel tags: trunk, main
20:13
merge 8.6 check-in: 21f26740 user: fvogel tags: unstable_tests
14:09
merge 8.6 check-in: fbf6e9bd user: dgp tags: core-8-6-13-rc
2022-10-17
20:45
Add a few more test adjustments check-in: 87d8b0cd user: culler tags: core-8-6-branch
19:31
Merge unstable_tests branch. This commit stabilizes several more tests that could sporadically fail on macOS. No test expected results were changed. check-in: f001c786 user: fvogel tags: core-8-6-branch
2022-10-10
21:24
Add a few other adjustments to unstable_tests Closed-Leaf check-in: 0599fb44 user: culler tags: unstable_plus

Changes to generic/tkBind.c.
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

struct PatSeq; /* forward declaration */

/* We need this array for bookkeeping the last matching modifier mask per pattern. */
TK_ARRAY_DEFINE(PSModMaskArr, unsigned);

typedef struct PSEntry {
    TK_DLIST_LINKS(PSEntry);	/* Makes this struct a double linked list; must be first entry. */
    Window window;		/* Window of last match. */
    struct PatSeq* psPtr;	/* Pointer to pattern sequence. */
    PSModMaskArr *lastModMaskArr;
    				/* Last matching modifier mask per pattern (except last pattern).
    				 * Only needed if pattern sequence is not single (more than one
				 * pattern), and if one of these patterns contains a non-zero
				 * modifier mask. */







|







139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

struct PatSeq; /* forward declaration */

/* We need this array for bookkeeping the last matching modifier mask per pattern. */
TK_ARRAY_DEFINE(PSModMaskArr, unsigned);

typedef struct PSEntry {
    TK_DLIST_LINKS(PSEntry);	/* Makes this struct a doubly linked list; must be first entry. */
    Window window;		/* Window of last match. */
    struct PatSeq* psPtr;	/* Pointer to pattern sequence. */
    PSModMaskArr *lastModMaskArr;
    				/* Last matching modifier mask per pattern (except last pattern).
    				 * Only needed if pattern sequence is not single (more than one
				 * pattern), and if one of these patterns contains a non-zero
				 * modifier mask. */
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
 * binding tables may exist at once, either because there are multiple
 * applications open, or because there are multiple domains within an
 * application with separate event bindings for each (for example, each canvas
 * widget has a separate binding table for associating events with the items
 * in the canvas).
 */

/* defining the whole Promotion_* stuff (array of PSList entries) */
TK_ARRAY_DEFINE(PromArr, PSList);

typedef struct Tk_BindingTable_ {
    Event eventInfo[TK_LASTEVENT];
    				/* Containing the most recent event for every event type. */
    PromArr *promArr;		/* Contains the promoted pattern sequences. */
    Event *curEvent;		/* Pointing to most recent event. */







|







184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
 * binding tables may exist at once, either because there are multiple
 * applications open, or because there are multiple domains within an
 * application with separate event bindings for each (for example, each canvas
 * widget has a separate binding table for associating events with the items
 * in the canvas).
 */

/* Defining the whole PromArr_* stuff (array of PSList entries) */
TK_ARRAY_DEFINE(PromArr, PSList);

typedef struct Tk_BindingTable_ {
    Event eventInfo[TK_LASTEVENT];
    				/* Containing the most recent event for every event type. */
    PromArr *promArr;		/* Contains the promoted pattern sequences. */
    Event *curEvent;		/* Pointing to most recent event. */
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429

/*
 * Flags for ModInfo structures:
 *
 * DOUBLE -		Non-zero means duplicate this event, e.g. for double-clicks.
 * TRIPLE -		Non-zero means triplicate this event, e.g. for triple-clicks.
 * QUADRUPLE -		Non-zero means quadruple this event, e.g. for 4-fold-clicks.
 * MULT_CLICKS -	Combination of all of above.
 */

#define DOUBLE		(1<<0)
#define TRIPLE		(1<<1)
#define QUADRUPLE	(1<<2)
#define MULT_CLICKS	(DOUBLE|TRIPLE|QUADRUPLE)








|







415
416
417
418
419
420
421
422
423
424
425
426
427
428
429

/*
 * Flags for ModInfo structures:
 *
 * DOUBLE -		Non-zero means duplicate this event, e.g. for double-clicks.
 * TRIPLE -		Non-zero means triplicate this event, e.g. for triple-clicks.
 * QUADRUPLE -		Non-zero means quadruple this event, e.g. for 4-fold-clicks.
 * MULT_CLICKS -	Combination of all the above.
 */

#define DOUBLE		(1<<0)
#define TRIPLE		(1<<1)
#define QUADRUPLE	(1<<2)
#define MULT_CLICKS	(DOUBLE|TRIPLE|QUADRUPLE)

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
#define	MAPREQ			(1<<20)
#define	CONFIGREQ		(1<<21)
#define	RESIZEREQ		(1<<22)
#define CIRCREQ			(1<<23)

/*
 * These structs agree with xkey for the fields type, serial, send_event, display,
 * window, root, subwindow, time, x, y, x_root, and y_root.  So when accessing
 * these fields we may pretend that we are using a struct xkey.
 */

#define HAS_XKEY_HEAD (KEY|BUTTON|MOTION|VIRTUAL|CROSSING|WHEEL)

/*
 * The xcrossing struct puts the state field in a different location, but the other







|







552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
#define	MAPREQ			(1<<20)
#define	CONFIGREQ		(1<<21)
#define	RESIZEREQ		(1<<22)
#define CIRCREQ			(1<<23)

/*
 * These structs agree with xkey for the fields type, serial, send_event, display,
 * window, root, subwindow, time, x, y, x_root, and y_root. So when accessing
 * these fields we may pretend that we are using a struct xkey.
 */

#define HAS_XKEY_HEAD (KEY|BUTTON|MOTION|VIRTUAL|CROSSING|WHEEL)

/*
 * The xcrossing struct puts the state field in a different location, but the other
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
static int IsOdd(int n) { return n & 1; }

static int TestNearbyTime(int lhs, int rhs) { return Abs(lhs - rhs) <= NEARBY_MS; }
static int TestNearbyCoords(int lhs, int rhs) { return Abs(lhs - rhs) <= NEARBY_PIXELS; }

static int
IsSubsetOf(
    unsigned lhsMask,	/* this is a subset */
    unsigned rhsMask)	/* of this bit field? */
{
    return (lhsMask & rhsMask) == lhsMask;
}

static const char*
SkipSpaces(
    const char* s)







|
|







750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
static int IsOdd(int n) { return n & 1; }

static int TestNearbyTime(int lhs, int rhs) { return Abs(lhs - rhs) <= NEARBY_MS; }
static int TestNearbyCoords(int lhs, int rhs) { return Abs(lhs - rhs) <= NEARBY_PIXELS; }

static int
IsSubsetOf(
    unsigned lhsMask,	/* Is this a subset... */
    unsigned rhsMask)	/* ...of this bit field? */
{
    return (lhsMask & rhsMask) == lhsMask;
}

static const char*
SkipSpaces(
    const char* s)
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
    unsigned eventType)
{
    return eventType == ButtonPress || eventType == ButtonRelease;
}

static int
MatchEventNearby(
    const XEvent *lhs,	/* previous button event */
    const XEvent *rhs)	/* current button event */
{
    assert(lhs);
    assert(rhs);
    assert(IsButtonEventType(lhs->type));
    assert(lhs->type == rhs->type);

    /* assert: lhs->xbutton.time <= rhs->xbutton.time */

    return TestNearbyTime(rhs->xbutton.time, lhs->xbutton.time)
	    && TestNearbyCoords(rhs->xbutton.x_root, lhs->xbutton.x_root)
	    && TestNearbyCoords(rhs->xbutton.y_root, lhs->xbutton.y_root);
}

static int
MatchEventRepeat(
    const XKeyEvent *lhs,	/* previous key event */
    const XKeyEvent *rhs)	/* current key event */
{
    assert(lhs);
    assert(rhs);
    assert(IsKeyEventType(lhs->type));
    assert(lhs->type == rhs->type);

    /* assert: lhs->time <= rhs->time */







|
|















|
|







854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
    unsigned eventType)
{
    return eventType == ButtonPress || eventType == ButtonRelease;
}

static int
MatchEventNearby(
    const XEvent *lhs,	/* Previous button event */
    const XEvent *rhs)	/* Current button event */
{
    assert(lhs);
    assert(rhs);
    assert(IsButtonEventType(lhs->type));
    assert(lhs->type == rhs->type);

    /* assert: lhs->xbutton.time <= rhs->xbutton.time */

    return TestNearbyTime(rhs->xbutton.time, lhs->xbutton.time)
	    && TestNearbyCoords(rhs->xbutton.x_root, lhs->xbutton.x_root)
	    && TestNearbyCoords(rhs->xbutton.y_root, lhs->xbutton.y_root);
}

static int
MatchEventRepeat(
    const XKeyEvent *lhs,	/* Previous key event */
    const XKeyEvent *rhs)	/* Current key event */
{
    assert(lhs);
    assert(rhs);
    assert(IsKeyEventType(lhs->type));
    assert(lhs->type == rhs->type);

    /* assert: lhs->time <= rhs->time */
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
{
    PatternTableKey key;
    Tcl_HashEntry *hPtr;

    assert(lookupTables);
    assert(eventPtr);

    /* otherwise on some systems the key contains uninitialized bytes */
    memset(&key, 0, sizeof(PatternTableKey));

    if (onlyConsiderDetailedEvents) {
	switch (eventPtr->xev.type) {
	case ButtonPress:   /* fallthru */
	case ButtonRelease: key.detail.info = eventPtr->xev.xbutton.button; break;
	case MotionNotify:  key.detail.info = ButtonNumberFromState(eventPtr->xev.xmotion.state); break;







|







1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
{
    PatternTableKey key;
    Tcl_HashEntry *hPtr;

    assert(lookupTables);
    assert(eventPtr);

    /* Otherwise on some systems the key contains uninitialized bytes. */
    memset(&key, 0, sizeof(PatternTableKey));

    if (onlyConsiderDetailedEvents) {
	switch (eventPtr->xev.type) {
	case ButtonPress:   /* fallthru */
	case ButtonRelease: key.detail.info = eventPtr->xev.xbutton.button; break;
	case MotionNotify:  key.detail.info = ButtonNumberFromState(eventPtr->xev.xmotion.state); break;
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

/*
 * Windoze compiler does not allow the definition of these static variables inside a function,
 * otherwise this should belong to function TkBindInit().
 */
TCL_DECLARE_MUTEX(bindMutex);
static int initialized = 0;

void
TkBindInit(
    TkMainInfo *mainPtr)	/* The newly created application. */
{
    BindInfo *bindInfoPtr;

    assert(mainPtr);

    /* otherwise virtual events can't be supported */
    assert(sizeof(XEvent) >= sizeof(XVirtualEvent));

    /* type of TkPattern.info is well defined? */
    assert(sizeof(Info) >= sizeof(KeySym));
    assert(sizeof(Info) >= sizeof(unsigned));

    /* ensure that our matching algorithm is working (when testing detail) */
    assert(sizeof(Detail) == sizeof(Tk_Uid));

    /* test expected indices of Button1..Button5, otherwise our button handling is not working */
    assert(Button1 == 1 && Button2 == 2 && Button3 == 3 && Button4 == 4 && Button5 == 5);
    assert(Button2Mask == (Button1Mask << 1));
    assert(Button3Mask == (Button1Mask << 2));
    assert(Button4Mask == (Button1Mask << 3));
    assert(Button5Mask == (Button1Mask << 4));

    /* test expected values of button motion masks, otherwise our button handling is not working */
    assert(Button1MotionMask == Button1Mask);
    assert(Button2MotionMask == Button2Mask);
    assert(Button3MotionMask == Button3Mask);
    assert(Button4MotionMask == Button4Mask);
    assert(Button5MotionMask == Button5Mask);

    /* because we expect zero if keySym is empty */
    assert(NoSymbol == 0L);

    /* this must be a union, not a struct, otherwise comparison with NULL will not work */
    assert(Tk_Offset(Detail, name) == Tk_Offset(Detail, info));

    /* we use some constraints about X*Event */
    assert(Tk_Offset(XButtonEvent, time) == Tk_Offset(XMotionEvent, time));
    assert(Tk_Offset(XButtonEvent, x_root) == Tk_Offset(XMotionEvent, x_root));
    assert(Tk_Offset(XButtonEvent, y_root) == Tk_Offset(XMotionEvent, y_root));
    assert(Tk_Offset(XCreateWindowEvent, border_width) == Tk_Offset(XConfigureEvent, border_width));
    assert(Tk_Offset(XCreateWindowEvent, width) == Tk_Offset(XConfigureEvent, width));
    assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XCirculateRequestEvent, window));
    assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XConfigureEvent, window));







|













|


|



|


|






|






|


|


|







1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
 * Side effects:
 *	Memory allocated.
 *
 *---------------------------------------------------------------------------
 */

/*
 * Windows compiler does not allow the definition of these static variables inside a function,
 * otherwise this should belong to function TkBindInit().
 */
TCL_DECLARE_MUTEX(bindMutex);
static int initialized = 0;

void
TkBindInit(
    TkMainInfo *mainPtr)	/* The newly created application. */
{
    BindInfo *bindInfoPtr;

    assert(mainPtr);

    /* Otherwise virtual events can't be supported. */
    assert(sizeof(XEvent) >= sizeof(XVirtualEvent));

    /* Is type of TkPattern.info well defined? */
    assert(sizeof(Info) >= sizeof(KeySym));
    assert(sizeof(Info) >= sizeof(unsigned));

    /* Ensure that our matching algorithm is working (when testing detail). */
    assert(sizeof(Detail) == sizeof(Tk_Uid));

    /* Test expected indices of Button1..Button5, otherwise our button handling is not working. */
    assert(Button1 == 1 && Button2 == 2 && Button3 == 3 && Button4 == 4 && Button5 == 5);
    assert(Button2Mask == (Button1Mask << 1));
    assert(Button3Mask == (Button1Mask << 2));
    assert(Button4Mask == (Button1Mask << 3));
    assert(Button5Mask == (Button1Mask << 4));

    /* Test expected values of button motion masks, otherwise our button handling is not working. */
    assert(Button1MotionMask == Button1Mask);
    assert(Button2MotionMask == Button2Mask);
    assert(Button3MotionMask == Button3Mask);
    assert(Button4MotionMask == Button4Mask);
    assert(Button5MotionMask == Button5Mask);

    /* Because we expect zero if keySym is empty. */
    assert(NoSymbol == 0L);

    /* This must be a union, not a struct, otherwise comparison with NULL will not work. */
    assert(Tk_Offset(Detail, name) == Tk_Offset(Detail, info));

    /* We use some constraints about X*Event. */
    assert(Tk_Offset(XButtonEvent, time) == Tk_Offset(XMotionEvent, time));
    assert(Tk_Offset(XButtonEvent, x_root) == Tk_Offset(XMotionEvent, x_root));
    assert(Tk_Offset(XButtonEvent, y_root) == Tk_Offset(XMotionEvent, y_root));
    assert(Tk_Offset(XCreateWindowEvent, border_width) == Tk_Offset(XConfigureEvent, border_width));
    assert(Tk_Offset(XCreateWindowEvent, width) == Tk_Offset(XConfigureEvent, width));
    assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XCirculateRequestEvent, window));
    assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XConfigureEvent, window));
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
    case KeyRelease: {
	int reset = 1;

	if (eventPtr->xkey.time) {
	    bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs();
	    bindInfoPtr->lastEventTime = eventPtr->xkey.time;
	}
	/* modifier keys should not influence button events */
	for (i = 0; i < (unsigned) dispPtr->numModKeyCodes; ++i) {
	    if (dispPtr->modKeyCodes[i] == eventPtr->xkey.keycode) {
		reset = 0;
	    }
	}
	if (reset) {
	    /* reset repetition count for button events */
	    bindPtr->eventInfo[ButtonPress].countAny = 0;
	    bindPtr->eventInfo[ButtonPress].countDetailed = 0;
	    bindPtr->eventInfo[ButtonRelease].countAny = 0;
	    bindPtr->eventInfo[ButtonRelease].countDetailed = 0;
	}
	break;
    }
    case ButtonPress:
    case ButtonRelease:
	/* reset repetition count for key events */
	bindPtr->eventInfo[KeyPress].countAny = 0;
	bindPtr->eventInfo[KeyPress].countDetailed = 0;
	bindPtr->eventInfo[KeyRelease].countAny = 0;
	bindPtr->eventInfo[KeyRelease].countDetailed = 0;
	/* fallthru */
    case MotionNotify:
	if (eventPtr->xmotion.time) {







|






|









|







2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
    case KeyRelease: {
	int reset = 1;

	if (eventPtr->xkey.time) {
	    bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs();
	    bindInfoPtr->lastEventTime = eventPtr->xkey.time;
	}
	/* Modifier keys should not influence button events. */
	for (i = 0; i < (unsigned) dispPtr->numModKeyCodes; ++i) {
	    if (dispPtr->modKeyCodes[i] == eventPtr->xkey.keycode) {
		reset = 0;
	    }
	}
	if (reset) {
	    /* Reset repetition count for button events. */
	    bindPtr->eventInfo[ButtonPress].countAny = 0;
	    bindPtr->eventInfo[ButtonPress].countDetailed = 0;
	    bindPtr->eventInfo[ButtonRelease].countAny = 0;
	    bindPtr->eventInfo[ButtonRelease].countDetailed = 0;
	}
	break;
    }
    case ButtonPress:
    case ButtonRelease:
	/* Reset repetition count for key events. */
	bindPtr->eventInfo[KeyPress].countAny = 0;
	bindPtr->eventInfo[KeyPress].countDetailed = 0;
	bindPtr->eventInfo[KeyRelease].countAny = 0;
	bindPtr->eventInfo[KeyRelease].countDetailed = 0;
	/* fallthru */
    case MotionNotify:
	if (eventPtr->xmotion.time) {
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
    bindPtr->curEvent = curEvent;
    physTables = &bindPtr->lookupTables;
    scriptCount = 0;
    arraySize = 0;
    Tcl_DStringInit(&scripts);

    if ((size_t) numObjects > SIZE_OF_ARRAY(matchPtrBuf)) {
	/* it's unrealistic that the buffer size is too small, but who knows? */
	matchPtrArr = (PatSeq **)ckalloc(numObjects*sizeof(matchPtrArr[0]));
    }
    memset(matchPtrArr, 0, numObjects*sizeof(matchPtrArr[0]));

    if (!PromArr_IsEmpty(bindPtr->promArr)) {
	for (k = 0; k < (unsigned) numObjects; ++k) {
	    psl[1] = PromArr_Last(bindPtr->promArr);







|







2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
    bindPtr->curEvent = curEvent;
    physTables = &bindPtr->lookupTables;
    scriptCount = 0;
    arraySize = 0;
    Tcl_DStringInit(&scripts);

    if ((size_t) numObjects > SIZE_OF_ARRAY(matchPtrBuf)) {
	/* It's unrealistic that the buffer size is too small, but who knows? */
	matchPtrArr = (PatSeq **)ckalloc(numObjects*sizeof(matchPtrArr[0]));
    }
    memset(matchPtrArr, 0, numObjects*sizeof(matchPtrArr[0]));

    if (!PromArr_IsEmpty(bindPtr->promArr)) {
	for (k = 0; k < (unsigned) numObjects; ++k) {
	    psl[1] = PromArr_Last(bindPtr->promArr);
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
	     * usually this list only contains one or two patterns.
	     */

	    for (i = PromArr_Size(bindPtr->promArr); i > 0; --i, --psl[0], --psl[1]) {
		psPtr[0] = MatchPatterns(dispPtr, bindPtr, psl[0], psl[1], i, curEvent, objArr[k], NULL);

		if (IsBetterMatch(matchPtrArr[k], psPtr[0])) {
		    /* we will process it later, because we still may find a pattern with better match */
		    matchPtrArr[k] = psPtr[0];
		}
		if (!PSList_IsEmpty(psl[1])) {
		    /* we have promoted sequences, adjust array size */
		    arraySize = Max(i + 1, arraySize);
		}
	    }
	}
    }

    /*







|



|







2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
	     * usually this list only contains one or two patterns.
	     */

	    for (i = PromArr_Size(bindPtr->promArr); i > 0; --i, --psl[0], --psl[1]) {
		psPtr[0] = MatchPatterns(dispPtr, bindPtr, psl[0], psl[1], i, curEvent, objArr[k], NULL);

		if (IsBetterMatch(matchPtrArr[k], psPtr[0])) {
		    /* We will process it later, because we still may find a pattern with better match. */
		    matchPtrArr[k] = psPtr[0];
		}
		if (!PSList_IsEmpty(psl[1])) {
		    /* We have promoted sequences, adjust array size. */
		    arraySize = Max(i + 1, arraySize);
		}
	    }
	}
    }

    /*
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424

	assert(psl[0] == NULL || psl[0] != psl[1]);

	psPtr[0] = MatchPatterns(dispPtr, bindPtr, psl[0], psSuccList, 0, curEvent, objArr[k], NULL);
	psPtr[1] = MatchPatterns(dispPtr, bindPtr, psl[1], psSuccList, 0, curEvent, objArr[k], NULL);

	if (!PSList_IsEmpty(psSuccList)) {
	    /* we have promoted sequences, adjust array size */
	    arraySize = Max(1u, arraySize);
	}

	bestPtr = psPtr[0] ? psPtr[0] : psPtr[1];

	if (matchPtrArr[k]) {
	    if (IsBetterMatch(matchPtrArr[k], bestPtr)) {







|







2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424

	assert(psl[0] == NULL || psl[0] != psl[1]);

	psPtr[0] = MatchPatterns(dispPtr, bindPtr, psl[0], psSuccList, 0, curEvent, objArr[k], NULL);
	psPtr[1] = MatchPatterns(dispPtr, bindPtr, psl[1], psSuccList, 0, curEvent, objArr[k], NULL);

	if (!PSList_IsEmpty(psSuccList)) {
	    /* We have promoted sequences, adjust array size. */
	    arraySize = Max(1u, arraySize);
	}

	bestPtr = psPtr[0] ? psPtr[0] : psPtr[1];

	if (matchPtrArr[k]) {
	    if (IsBetterMatch(matchPtrArr[k], bestPtr)) {
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
		    matchPtrArr[k] = matchPtr;
		}
	    }
	}

	if (matchPtrArr[k]) {
	    ExpandPercents(winPtr, matchPtrArr[k]->script, curEvent, scriptCount++, &scripts);
	    /* nul is added to the scripts string to separate the various scripts */
	    Tcl_DStringAppend(&scripts, "", 1);
	}
    }

    PromArr_SetSize(bindPtr->promArr, arraySize);

    /*







|







2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
		    matchPtrArr[k] = matchPtr;
		}
	    }
	}

	if (matchPtrArr[k]) {
	    ExpandPercents(winPtr, matchPtrArr[k]->script, curEvent, scriptCount++, &scripts);
	    /* Null is added to the scripts string to separate the various scripts. */
	    Tcl_DStringAppend(&scripts, "", 1);
	}
    }

    PromArr_SetSize(bindPtr->promArr, arraySize);

    /*
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
		    }
		    break;
		}
	    }
	}

	if (!PSList_IsEmpty(psList)) {
	    /* we still have promoted sequences, adjust array size */
	    newArraySize = Max(i + 1, newArraySize);
	}
    }

    PromArr_SetSize(bindPtr->promArr, newArraySize);

    if (matchPtrArr != matchPtrBuf) {
	ckfree(matchPtrArr);
    }

    if (Tcl_DStringLength(&scripts) == 0) {
	return; /* nothing to do */
    }

    /*
     * Now go back through and evaluate the binding for each object, in order,
     * dealing with "break" and "continue" exceptions appropriately.
     *
     * There are two tricks here:







|











|







2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
		    }
		    break;
		}
	    }
	}

	if (!PSList_IsEmpty(psList)) {
	    /* We still have promoted sequences, adjust array size. */
	    newArraySize = Max(i + 1, newArraySize);
	}
    }

    PromArr_SetSize(bindPtr->promArr, newArraySize);

    if (matchPtrArr != matchPtrBuf) {
	ckfree(matchPtrArr);
    }

    if (Tcl_DStringLength(&scripts) == 0) {
	return; /* Nothing to do. */
    }

    /*
     * Now go back through and evaluate the binding for each object, in order,
     * dealing with "break" and "continue" exceptions appropriately.
     *
     * There are two tricks here:
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690

    if (*physPtrPtr) {
	const TkPattern *physPatPtr = (*physPtrPtr)->pats;
	const TkPattern *virtPatPtr = psPtr->pats;

	if (physPatPtr->info || !virtPatPtr->info) {
	    if (IsSubsetOf(virtPatPtr->modMask, physPatPtr->modMask)) {
		return 0; /* we cannot surpass this match */
	    }
	}
    }

    /* otherwise on some systems the key contains uninitialized bytes */
    memset(&key, 0, sizeof(key));

    key.object = object;
    key.type = VirtualEvent;
    owners = psPtr->ptr.owners;

    for (i = 0; i < VirtOwners_Size(owners); ++i) {







|




|







2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690

    if (*physPtrPtr) {
	const TkPattern *physPatPtr = (*physPtrPtr)->pats;
	const TkPattern *virtPatPtr = psPtr->pats;

	if (physPatPtr->info || !virtPatPtr->info) {
	    if (IsSubsetOf(virtPatPtr->modMask, physPatPtr->modMask)) {
		return 0; /* We cannot surpass this match. */
	    }
	}
    }

    /* Otherwise on some systems the key contains uninitialized bytes. */
    memset(&key, 0, sizeof(key));

    key.object = object;
    key.type = VirtualEvent;
    owners = psPtr->ptr.owners;

    for (i = 0; i < VirtOwners_Size(owners); ++i) {
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
    return 0;
}

/* helper function */
static int
Compare(
    const PatSeq *fstMatchPtr,
    const PatSeq *sndMatchPtr) /* most recent match */
{
    int diff;

    if (!fstMatchPtr) { return +1; }
    assert(sndMatchPtr);
    diff = CountSpecialized(fstMatchPtr, sndMatchPtr);
    return diff ? diff : (int) sndMatchPtr->count - (int) fstMatchPtr->count;







|







2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
    return 0;
}

/* helper function */
static int
Compare(
    const PatSeq *fstMatchPtr,
    const PatSeq *sndMatchPtr) /* Most recent match. */
{
    int diff;

    if (!fstMatchPtr) { return +1; }
    assert(sndMatchPtr);
    diff = CountSpecialized(fstMatchPtr, sndMatchPtr);
    return diff ? diff : (int) sndMatchPtr->count - (int) fstMatchPtr->count;
2757
2758
2759
2760
2761
2762
2763














2764
2765
2766
2767
2768
2769
2770
    /* Finally compare modifier masks of last pattern. */

    if (IsSubsetOf(fstModMask, sndModMask)) { ++sndCount; }
    if (IsSubsetOf(sndModMask, fstModMask)) { ++fstCount; }

    return fstCount - sndCount;
}















static PatSeq *
MatchPatterns(
    TkDisplay *dispPtr,		/* Display from which the event came. */
    Tk_BindingTable bindPtr,	/* Table in which to look for bindings. */
    PSList *psList,		/* List of potentially matching patterns, can be NULL. */
    PSList *psSuccList,		/* Add all matching higher-level pattern sequences to this list.







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







2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
    /* Finally compare modifier masks of last pattern. */

    if (IsSubsetOf(fstModMask, sndModMask)) { ++sndCount; }
    if (IsSubsetOf(sndModMask, fstModMask)) { ++fstCount; }

    return fstCount - sndCount;
}

/* helper function */
static int
IsPSInPSList(
    const PatSeq *psPtr,   /* Is this pattern sequence... */
    const PSList *psList)  /* ...an element of this list of patterns sequence? */
{
    PSEntry *psEntry;

    TK_DLIST_FOREACH(psEntry, psList) {
        if (psEntry->psPtr == psPtr) { return 1; }
    }
    return 0;
}

static PatSeq *
MatchPatterns(
    TkDisplay *dispPtr,		/* Display from which the event came. */
    Tk_BindingTable bindPtr,	/* Table in which to look for bindings. */
    PSList *psList,		/* List of potentially matching patterns, can be NULL. */
    PSList *psSuccList,		/* Add all matching higher-level pattern sequences to this list.
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
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
2876
2877
	    assert(psPtr->numPats > patIndex);

	    if (psPtr->object
		    ? psPtr->object == object
		    : VirtPatIsBound(bindPtr, psPtr, object, physPtrPtr)) {
		TkPattern *patPtr = psPtr->pats + patIndex;

                /* ignore modifier key events, and KeyRelease events if the current event
                 * is of a different type (e.g. a Button event)
                 */
                psEntry->keepIt = isModKeyOnly || \
                        ((patPtr->eventType != (unsigned) curEvent->xev.type) && curEvent->xev.type == KeyRelease);

		if (patPtr->eventType == (unsigned) curEvent->xev.type
			&& (curEvent->xev.type != CreateNotify
				|| curEvent->xev.xcreatewindow.parent == window)
			&& (!patPtr->name || patPtr->name == curEvent->detail.name)
			&& (!patPtr->info || patPtr->info == curEvent->detail.info)) {
		    /*
		     * Resolve the modifier mask for Alt and Mod keys. Unfortunately this
		     * cannot be done in ParseEventDescription, otherwise this function would
		     * be the better place.
		     */
		    unsigned modMask = ResolveModifiers(dispPtr, patPtr->modMask);
		    unsigned curModMask = ResolveModifiers(dispPtr, bindPtr->curModMask);

		    psEntry->expired = 1; /* remove it from promotion list */
                    psEntry->keepIt = 0; /* don't keep matching patterns */

		    if (IsSubsetOf(modMask, curModMask)) {
			unsigned count = patPtr->info ? curEvent->countDetailed : curEvent->countAny;

			if (patIndex < PSModMaskArr_Size(psEntry->lastModMaskArr)) {
			    PSModMaskArr_Set(psEntry->lastModMaskArr, patIndex, &modMask);
			}

			/*
			 * This pattern is finally matching.
			 */

			if (psPtr->numPats == patIndex + 1) {
			    if (patPtr->count <= count) {
				/*
				 * This is also a final pattern.
				 * We always prefer the pattern with better match.
				 * If completely equal than prefer most recently defined pattern.
				 */

				int cmp = Compare(bestPtr, psPtr);

				if (cmp == 0) {







|


















|
|















|







2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
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
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
	    assert(psPtr->numPats > patIndex);

	    if (psPtr->object
		    ? psPtr->object == object
		    : VirtPatIsBound(bindPtr, psPtr, object, physPtrPtr)) {
		TkPattern *patPtr = psPtr->pats + patIndex;

                /* Ignore modifier key events, and KeyRelease events if the current event
                 * is of a different type (e.g. a Button event)
                 */
                psEntry->keepIt = isModKeyOnly || \
                        ((patPtr->eventType != (unsigned) curEvent->xev.type) && curEvent->xev.type == KeyRelease);

		if (patPtr->eventType == (unsigned) curEvent->xev.type
			&& (curEvent->xev.type != CreateNotify
				|| curEvent->xev.xcreatewindow.parent == window)
			&& (!patPtr->name || patPtr->name == curEvent->detail.name)
			&& (!patPtr->info || patPtr->info == curEvent->detail.info)) {
		    /*
		     * Resolve the modifier mask for Alt and Mod keys. Unfortunately this
		     * cannot be done in ParseEventDescription, otherwise this function would
		     * be the better place.
		     */
		    unsigned modMask = ResolveModifiers(dispPtr, patPtr->modMask);
		    unsigned curModMask = ResolveModifiers(dispPtr, bindPtr->curModMask);

		    psEntry->expired = 1; /* Remove it from promotion list. */
                    psEntry->keepIt = 0;  /* Don't keep matching patterns. */

		    if (IsSubsetOf(modMask, curModMask)) {
			unsigned count = patPtr->info ? curEvent->countDetailed : curEvent->countAny;

			if (patIndex < PSModMaskArr_Size(psEntry->lastModMaskArr)) {
			    PSModMaskArr_Set(psEntry->lastModMaskArr, patIndex, &modMask);
			}

			/*
			 * This pattern is finally matching.
			 */

			if (psPtr->numPats == patIndex + 1) {
			    if (patPtr->count <= count) {
				/*
				 * This is also a final pattern (i.e. the pattern sequence is complete).
				 * We always prefer the pattern with better match.
				 * If completely equal than prefer most recently defined pattern.
				 */

				int cmp = Compare(bestPtr, psPtr);

				if (cmp == 0) {
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896

2897
2898

2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916








2917
2918
2919
2920
2921
2922
2923
				    bestModMaskArr = psEntry->lastModMaskArr;
				    if (physPtrPtr) {
					bestPhysPtr = *physPtrPtr;
				    }
				}
			    } else {
				DEBUG(psEntry->expired = 0;)
				psEntry->keepIt = 1; /* don't remove it from promotion list */
			    }
			} else if (psSuccList) {
			    /*
			     * Not a final pattern, but matching, so promote it to next level.

			     * But do not promote if count of current pattern is not yet reached.
			     */

			    if (patPtr->count == psEntry->count) {
				PSEntry *psNewEntry;

				assert(!patPtr->name);
				psNewEntry = MakeListEntry(
				    &bindPtr->lookupTables.entryPool, psPtr, psPtr->modMaskUsed);
				if (!PSModMaskArr_IsEmpty(psNewEntry->lastModMaskArr)) {
				    PSModMaskArr_Set(psNewEntry->lastModMaskArr, patIndex, &modMask);
				}
				assert(psNewEntry->keepIt);
				assert(psNewEntry->count == 1u);
				PSList_Append(psSuccList, psNewEntry);
				psNewEntry->window = window; /* bind to current window */
			    } else {
				assert(psEntry->count < patPtr->count);
				DEBUG(psEntry->expired = 0;)
				psEntry->count += 1;
				psEntry->keepIt = 1; /* don't remove it from promotion list */








			    }
			}
		    }
		}
	    }
	}
    }







|



|
>


>
|
|

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







2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
				    bestModMaskArr = psEntry->lastModMaskArr;
				    if (physPtrPtr) {
					bestPhysPtr = *physPtrPtr;
				    }
				}
			    } else {
				DEBUG(psEntry->expired = 0;)
				psEntry->keepIt = 1; /* Don't remove it from promotion list. */
			    }
			} else if (psSuccList) {
			    /*
			     * Not a final pattern, but matching (i.e. successive patterns match the pattern sequence so far),
			     * so promote the pattern sequence to next level if not already promoted in the success list.
			     * But do not promote if count of current pattern is not yet reached.
			     */
			    if (!IsPSInPSList(psPtr, psSuccList)) {
				if (patPtr->count == psEntry->count) {
				    PSEntry *psNewEntry;

				    assert(!patPtr->name);
				    psNewEntry = MakeListEntry(
					&bindPtr->lookupTables.entryPool, psPtr, psPtr->modMaskUsed);
				    if (!PSModMaskArr_IsEmpty(psNewEntry->lastModMaskArr)) {
					PSModMaskArr_Set(psNewEntry->lastModMaskArr, patIndex, &modMask);
				    }
				    assert(psNewEntry->keepIt);
				    assert(psNewEntry->count == 1u);
				    PSList_Append(psSuccList, psNewEntry);
				    psNewEntry->window = window; /* Bind to current window. */
				} else {
				    assert(psEntry->count < patPtr->count);
				    DEBUG(psEntry->expired = 0;)
				    psEntry->count += 1;
				    psEntry->keepIt = 1; /* Don't remove it from promotion list. */
				}
			    } else {
			        /*
				 * Pattern sequence is already present in the success list.
				 */

				DEBUG(psEntry->expired = 0;)
				psEntry->keepIt = 1; /* Don't remove it from promotion list. */
			    }
			}
		    }
		}
	    }
	}
    }
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
    assert(eventString);

    if (!(virtUid = GetVirtualEventUid(interp, virtString))) {
	return 0;
    }

    /*
     * Find/create physical event
     */

    if (!(psPtr = FindSequence(interp, &vetPtr->lookupTables, NULL, eventString, 1, 0, NULL))) {
	return 0;
    }
    assert(TEST_PSENTRY(psPtr));








|







3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
    assert(eventString);

    if (!(virtUid = GetVirtualEventUid(interp, virtString))) {
	return 0;
    }

    /*
     * Find/create physical event.
     */

    if (!(psPtr = FindSequence(interp, &vetPtr->lookupTables, NULL, eventString, 1, 0, NULL))) {
	return 0;
    }
    assert(TEST_PSENTRY(psPtr));

3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667

	assert(TEST_PSENTRY(psPtr));

	if (!eventPSPtr || psPtr == eventPSPtr) {
	    VirtOwners *owners = psPtr->ptr.owners;
	    int iVirt = VirtOwners_Find(owners, vhPtr);

	    assert(iVirt != -1); /* otherwise we couldn't find owner, and this should not happen */

	    /*
	     * Remove association between this physical event and the given
	     * virtual event that it triggers.
	     */

	    if (VirtOwners_Size(owners) > 1) {







|







3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691

	assert(TEST_PSENTRY(psPtr));

	if (!eventPSPtr || psPtr == eventPSPtr) {
	    VirtOwners *owners = psPtr->ptr.owners;
	    int iVirt = VirtOwners_Find(owners, vhPtr);

	    assert(iVirt != -1); /* Otherwise we couldn't find owner, and this should not happen. */

	    /*
	     * Remove association between this physical event and the given
	     * virtual event that it triggers.
	     */

	    if (VirtOwners_Size(owners) > 1) {
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
    unsigned count = 1;

    assert(eventStringPtr);
    assert(patPtr);
    assert(eventMaskPtr);

    p = *eventStringPtr;
    memset(patPtr, 0, sizeof(TkPattern)); /* otherwise memcmp doesn't work */

    /*
     * Handle simple ASCII characters.
     */

    if (*p != '<') {
	char string[2];







|







4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
    unsigned count = 1;

    assert(eventStringPtr);
    assert(patPtr);
    assert(eventMaskPtr);

    p = *eventStringPtr;
    memset(patPtr, 0, sizeof(TkPattern)); /* Otherwise memcmp doesn't work. */

    /*
     * Handle simple ASCII characters.
     */

    if (*p != '<') {
	char string[2];
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
	    }
	    if (*field) {
		unsigned button = GetButtonNumber(field);

		if ((eventFlags & BUTTON)
			|| (button && eventFlags == 0)
			|| (SUPPORT_ADDITIONAL_MOTION_SYNTAX && (eventFlags & MOTION) && button == 0)) {
		    /* This must be a button (or bad motion) event */
		    if (button == 0) {
			return FinalizeParseEventDescription(
				interp,
				patPtr, 0,
				Tcl_ObjPrintf("bad button number \"%s\"", field), "BUTTON");
		    }
		    patPtr->info = button;







|







4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
	    }
	    if (*field) {
		unsigned button = GetButtonNumber(field);

		if ((eventFlags & BUTTON)
			|| (button && eventFlags == 0)
			|| (SUPPORT_ADDITIONAL_MOTION_SYNTAX && (eventFlags & MOTION) && button == 0)) {
		    /* This must be a button (or bad motion) event. */
		    if (button == 0) {
			return FinalizeParseEventDescription(
				interp,
				patPtr, 0,
				Tcl_ObjPrintf("bad button number \"%s\"", field), "BUTTON");
		    }
		    patPtr->info = button;
Changes to tests/bind.test.
6982
6983
6984
6985
6986
6987
6988





















6989
6990
6991
6992
6993
6994
6995
    # are at (10,10) inside the label widget as requested by the warping
    expr {$x1==$x2 && $y1==$y2 && $x1==[winfo rootx .top.l]+10 \
                               && $y1==[winfo rooty .top.l]+10}
} -cleanup {
    destroy .top
    unset x1 y1 x2 y2
} -result 1






















# cleanup
cleanupTests
return

# vi:set ts=4 sw=4 et:
# Local Variables:







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







6982
6983
6984
6985
6986
6987
6988
6989
6990
6991
6992
6993
6994
6995
6996
6997
6998
6999
7000
7001
7002
7003
7004
7005
7006
7007
7008
7009
7010
7011
7012
7013
7014
7015
7016
    # are at (10,10) inside the label widget as requested by the warping
    expr {$x1==$x2 && $y1==$y2 && $x1==[winfo rootx .top.l]+10 \
                               && $y1==[winfo rooty .top.l]+10}
} -cleanup {
    destroy .top
    unset x1 y1 x2 y2
} -result 1

test bind-37.1 {Promotion tables do not contain duplicate sequences, bug [43573999ca]} -body {
    proc A {} {
       bind .c <B1-Motion><Enter> {}
       set myv(a) 1
       set b [array get myv]
       bind .c <B1-Motion><Enter> "puts Trigger"
    }
    pack [canvas .c]
    bind .c <ButtonRelease-1>  "A"
    A
    update
    event generate .c <Button-1>
    event generate .c <B1-Motion>
    event generate .c <B1-Motion>
    event generate .c <B1-Motion>
    event generate .c <ButtonRelease-1>
    event generate .c <B1-Motion>
} -cleanup {
    destroy .c
} -returnCodes ok -result {}  ; # shall not crash (assertion failed)

# cleanup
cleanupTests
return

# vi:set ts=4 sw=4 et:
# Local Variables: