From 20e5273f5b6208576bf9bc900e4cff39d724cb7f Mon Sep 17 00:00:00 2001 From: fvogel Date: Sat, 5 Dec 2015 13:31:00 +0000 Subject: Fix for bug [ff8a1e55a2] - Filling a never-mapped text widget is CPU hungry - Patch from Koen Danckaert --- generic/tkTextDisp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index cfe6e7a..0665ba7 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -2923,7 +2923,8 @@ AsyncUpdateLineMetrics( return; } - if (dInfoPtr->flags & REDRAW_PENDING) { + if ((dInfoPtr->flags & REDRAW_PENDING) || !Tk_IsMapped(textPtr->tkwin) + || dInfoPtr->maxX <= dInfoPtr->x || dInfoPtr->maxY <= dInfoPtr->y) { dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, clientData); return; -- cgit v0.12 From 1c20b0f7e0b8d0e66ff9b4325dbc2859968f52fd Mon Sep 17 00:00:00 2001 From: fvogel Date: Sat, 5 Dec 2015 13:48:24 +0000 Subject: Fix for bug [1739605] - [text see] misbehaves following widget create/populate - Patch from Koen Danckaert --- generic/tkTextDisp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index cfe6e7a..851e17a 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -5237,6 +5237,15 @@ TkTextSetYView( } /* + * If the window height is smaller than the line height, prefer to make + * the top of the line visible. + */ + + if (dInfoPtr->maxY - dInfoPtr->y < lineHeight) { + bottomY = lineHeight; + } + + /* * Our job now is to arrange the display so that indexPtr appears as low * on the screen as possible but with its bottom no lower than bottomY. * BottomY is the bottom of the window if the desired line is just below -- cgit v0.12 From 98378541b701acb3b2821a3b9f1e0a23fb57bcfc Mon Sep 17 00:00:00 2001 From: fvogel Date: Sun, 6 Dec 2015 19:57:34 +0000 Subject: Added non-regression test case: textDisp-11.21 --- tests/textDisp.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/textDisp.test b/tests/textDisp.test index 5508d7c..038eccd 100644 --- a/tests/textDisp.test +++ b/tests/textDisp.test @@ -1574,6 +1574,17 @@ test textDisp-11.20 {TkTextSetYView, see in elided lines} { # this shall not crash (null chunkPtr in TkTextSeeCmd is tested) .top.t see 3.0 } {} +test textDisp-11.21 {TkTextSetYView, window height smaller than the line height} { + .top.t delete 1.0 end + for {set i 1} {$i <= 10} {incr i} { + .top.t insert end "Line $i\n" + } + set lineheight [font metrics [.top.t cget -font] -linespace] + wm geometry .top 200x[expr {$lineheight / 2}] + update + .top.t see 1.0 + .top.t index @0,[expr {$lineheight - 2}] +} {1.0} .t configure -wrap word .t delete 50.0 51.0 -- cgit v0.12 From c1c79b52233b43b276d2b2933dade1563b16f0c5 Mon Sep 17 00:00:00 2001 From: fvogel Date: Mon, 7 Dec 2015 21:29:13 +0000 Subject: Reverted [d29847c6] since there is a better patch --- generic/tkTextDisp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index 0665ba7..cfe6e7a 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -2923,8 +2923,7 @@ AsyncUpdateLineMetrics( return; } - if ((dInfoPtr->flags & REDRAW_PENDING) || !Tk_IsMapped(textPtr->tkwin) - || dInfoPtr->maxX <= dInfoPtr->x || dInfoPtr->maxY <= dInfoPtr->y) { + if (dInfoPtr->flags & REDRAW_PENDING) { dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, clientData); return; -- cgit v0.12 From 7aa2342349dcaec45b76ecf2f5c6b0fe8ec33ae1 Mon Sep 17 00:00:00 2001 From: fvogel Date: Mon, 7 Dec 2015 21:36:25 +0000 Subject: Better patch for bug [ff8a1e55a2] - Filling a never-mapped text widget is CPU hungry - Patch from Koen Danckaert --- generic/tkTextDisp.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index cfe6e7a..8a9d0e8 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -658,17 +658,8 @@ TkTextCreateDInfo( dInfoPtr->metricEpoch = -1; dInfoPtr->metricIndex.textPtr = NULL; dInfoPtr->metricIndex.linePtr = NULL; - - /* - * Add a refCount for each of the idle call-backs. - */ - - textPtr->refCount++; - dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0, - AsyncUpdateLineMetrics, (ClientData) textPtr); - textPtr->refCount++; - dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, - AsyncUpdateYScrollbar, (ClientData) textPtr); + dInfoPtr->lineUpdateTimer = NULL; + dInfoPtr->scrollbarTimer = NULL; textPtr->dInfoPtr = dInfoPtr; } @@ -2912,9 +2903,10 @@ AsyncUpdateLineMetrics( dInfoPtr->lineUpdateTimer = NULL; - if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED) + || !Tk_IsMapped(textPtr->tkwin)) { /* - * The widget has been deleted. Don't do anything. + * The widget has been deleted, or is not mapped. Don't do anything. */ if (--textPtr->refCount == 0) { -- cgit v0.12 From 4ffa86ad7e16a62ad85e9fcaeeeeec8b054b66e3 Mon Sep 17 00:00:00 2001 From: fvogel Date: Thu, 10 Dec 2015 17:07:13 +0000 Subject: Removed duplicate test: 'entry-23.1' in spinbox.test is the same as 'entry-21.1' in entry.test --- tests/spinbox.test | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/spinbox.test b/tests/spinbox.test index 430e176..88e4d44 100644 --- a/tests/spinbox.test +++ b/tests/spinbox.test @@ -1568,20 +1568,6 @@ test spinbox-22.3 {spinbox config, -from changes SF bug 559078} { set val } {6} -test entry-23.1 {selection present while disabled, bug 637828} { - destroy .e - entry .e - .e insert end 0123456789 - .e select from 3 - .e select to 6 - set out [.e selection present] - .e configure -state disabled - # still return 1 when disabled, because 'selection get' will work, - # but selection cannot be changed (new behavior since 8.4) - .e select to 9 - lappend out [.e selection present] [selection get] -} {1 1 345} - destroy .e catch {unset ::e ::vVals} -- cgit v0.12 From 2f823dce353535fda34fff8883078e034675eec3 Mon Sep 17 00:00:00 2001 From: fvogel Date: Thu, 10 Dec 2015 17:21:07 +0000 Subject: Fixed bug [1700065] - error in trace proc on textvariable doesn't trigger bgerror --- generic/tkEntry.c | 88 ++++++++++++++++++++++++++++++++++++++---------------- tests/entry.test | 21 ++++++++++--- tests/spinbox.test | 15 ++++++++++ 3 files changed, 94 insertions(+), 30 deletions(-) diff --git a/generic/tkEntry.c b/generic/tkEntry.c index f6ff9b9..a303bea 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -391,7 +391,7 @@ static const char *selElementNames[] = { static int ConfigureEntry(Tcl_Interp *interp, Entry *entryPtr, int objc, Tcl_Obj *const objv[], int flags); -static void DeleteChars(Entry *entryPtr, int index, int count); +static int DeleteChars(Entry *entryPtr, int index, int count); static void DestroyEntry(char *memPtr); static void DisplayEntry(ClientData clientData); static void EntryBlinkProc(ClientData clientData); @@ -417,7 +417,7 @@ static int EntryValidateChange(Entry *entryPtr, char *change, static void ExpandPercents(Entry *entryPtr, const char *before, const char *change, const char *newStr, int index, int type, Tcl_DString *dsPtr); -static void EntryValueChanged(Entry *entryPtr, +static int EntryValueChanged(Entry *entryPtr, const char *newValue); static void EntryVisibleRange(Entry *entryPtr, double *firstPtr, double *lastPtr); @@ -427,7 +427,7 @@ static int EntryWidgetObjCmd(ClientData clientData, static void EntryWorldChanged(ClientData instanceData); static int GetEntryIndex(Tcl_Interp *interp, Entry *entryPtr, char *string, int *indexPtr); -static void InsertChars(Entry *entryPtr, int index, char *string); +static int InsertChars(Entry *entryPtr, int index, char *string); /* * These forward declarations are the spinbox specific ones: @@ -663,7 +663,7 @@ EntryWidgetObjCmd( break; case COMMAND_DELETE: { - int first, last; + int first, last, code; if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?"); @@ -680,7 +680,10 @@ EntryWidgetObjCmd( goto error; } if ((last >= first) && (entryPtr->state == STATE_NORMAL)) { - DeleteChars(entryPtr, first, last - first); + code = DeleteChars(entryPtr, first, last - first); + if (code != TCL_OK) { + goto error; + } } break; } @@ -721,7 +724,7 @@ EntryWidgetObjCmd( } case COMMAND_INSERT: { - int index; + int index, code; if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "index text"); @@ -732,7 +735,10 @@ EntryWidgetObjCmd( goto error; } if (entryPtr->state == STATE_NORMAL) { - InsertChars(entryPtr, index, Tcl_GetString(objv[3])); + code = InsertChars(entryPtr, index, Tcl_GetString(objv[3])); + if (code != TCL_OK) { + goto error; + } } break; } @@ -1291,6 +1297,9 @@ ConfigureEntry( /* * If the entry is tied to the value of a variable, create the variable if * it doesn't exist, and set the entry's value from the variable's value. + * No checking of the return value of EntryValueChanged is needed since it + * can only return TCL_ERROR if there is a trace on the textvariable and + * since any existing trace on it was eliminated above. */ if (entryPtr->textVarName != NULL) { @@ -1999,7 +2008,7 @@ EntryComputeGeometry( *---------------------------------------------------------------------- */ -static void +static int InsertChars( Entry *entryPtr, /* Entry that is to get the new elements. */ int index, /* Add the new elements before this character @@ -2017,7 +2026,7 @@ InsertChars( byteIndex = Tcl_UtfAtIndex(string, index) - string; byteCount = strlen(value); if (byteCount == 0) { - return; + return TCL_OK; } newByteCount = entryPtr->numBytes + byteCount + 1; @@ -2031,7 +2040,7 @@ InsertChars( EntryValidateChange(entryPtr, value, newStr, index, VALIDATE_INSERT) != TCL_OK) { ckfree(newStr); - return; + return TCL_OK; } ckfree((char *)string); @@ -2079,7 +2088,7 @@ InsertChars( if (entryPtr->insertPos >= index) { entryPtr->insertPos += charsAdded; } - EntryValueChanged(entryPtr, NULL); + return EntryValueChanged(entryPtr, NULL); } /* @@ -2099,7 +2108,7 @@ InsertChars( *---------------------------------------------------------------------- */ -static void +static int DeleteChars( Entry *entryPtr, /* Entry widget to modify. */ int index, /* Index of first character to delete. */ @@ -2113,7 +2122,7 @@ DeleteChars( count = entryPtr->numChars - index; } if (count <= 0) { - return; + return TCL_OK; } string = entryPtr->string; @@ -2135,7 +2144,7 @@ DeleteChars( VALIDATE_DELETE) != TCL_OK) { ckfree(newStr); ckfree(toDelete); - return; + return TCL_OK; } ckfree(toDelete); @@ -2194,7 +2203,7 @@ DeleteChars( entryPtr->insertPos = index; } } - EntryValueChanged(entryPtr, NULL); + return EntryValueChanged(entryPtr, NULL); } /* @@ -2215,7 +2224,7 @@ DeleteChars( *---------------------------------------------------------------------- */ -static void +static int EntryValueChanged( Entry *entryPtr, /* Entry whose value just changed. */ const char *newValue) /* If this value is not NULL, we first force @@ -2229,7 +2238,7 @@ EntryValueChanged( newValue = NULL; } else { newValue = Tcl_SetVar(entryPtr->interp, entryPtr->textVarName, - entryPtr->string, TCL_GLOBAL_ONLY); + entryPtr->string, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); } if ((newValue != NULL) && (strcmp(newValue, entryPtr->string) != 0)) { @@ -2251,6 +2260,17 @@ EntryValueChanged( EntryComputeGeometry(entryPtr); EventuallyRedraw(entryPtr); } + + /* + * An error may have happened when setting the textvariable in case there + * is a trace on that variable and the trace proc triggered an error. + * Signal this error. + */ + + if ((entryPtr->textVarName != NULL) && (newValue == NULL)) { + return TCL_ERROR; + } + return TCL_OK; } /* @@ -3703,7 +3723,7 @@ SpinboxWidgetObjCmd( break; case SB_CMD_DELETE: { - int first, last; + int first, last, code; if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?"); @@ -3722,7 +3742,10 @@ SpinboxWidgetObjCmd( } } if ((last >= first) && (entryPtr->state == STATE_NORMAL)) { - DeleteChars(entryPtr, first, last - first); + code = DeleteChars(entryPtr, first, last - first); + if (code != TCL_OK) { + goto error; + } } break; } @@ -3782,7 +3805,7 @@ SpinboxWidgetObjCmd( } case SB_CMD_INSERT: { - int index; + int index, code; if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "index text"); @@ -3793,7 +3816,10 @@ SpinboxWidgetObjCmd( goto error; } if (entryPtr->state == STATE_NORMAL) { - InsertChars(entryPtr, index, Tcl_GetString(objv[3])); + code = InsertChars(entryPtr, index, Tcl_GetString(objv[3])); + if (code != TCL_OK) { + goto error; + } } break; } @@ -4001,16 +4027,22 @@ SpinboxWidgetObjCmd( break; } - case SB_CMD_SET: + case SB_CMD_SET: { + int code = TCL_OK; + if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "?string?"); goto error; } if (objc == 3) { - EntryValueChanged(entryPtr, Tcl_GetString(objv[2])); + code = EntryValueChanged(entryPtr, Tcl_GetString(objv[2])); + if (code != TCL_OK) { + goto error; + } } Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1); break; + } case SB_CMD_VALIDATE: { int code; @@ -4151,7 +4183,7 @@ GetSpinboxElement( * TCL_OK. * * Side effects: - * An background error condition may arise when invoking the callback. + * A background error condition may arise when invoking the callback. * The widget value may change. * *-------------------------------------------------------------- @@ -4182,6 +4214,7 @@ SpinboxInvoke( return TCL_OK; } + code = TCL_OK; if (fabs(sbPtr->increment) > MIN_DBL_VAL) { if (sbPtr->listObj != NULL) { Tcl_Obj *objPtr; @@ -4227,7 +4260,7 @@ SpinboxInvoke( } } Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr); - EntryValueChanged(entryPtr, Tcl_GetString(objPtr)); + code = EntryValueChanged(entryPtr, Tcl_GetString(objPtr)); } else if (!DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue)) { double dvalue; @@ -4274,9 +4307,12 @@ SpinboxInvoke( } } sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue); - EntryValueChanged(entryPtr, sbPtr->formatBuf); + code = EntryValueChanged(entryPtr, sbPtr->formatBuf); } } + if (code != TCL_OK) { + return TCL_ERROR; + } if (sbPtr->command != NULL) { Tcl_DStringInit(&script); diff --git a/tests/entry.test b/tests/entry.test index 27acfc1..da8f280 100644 --- a/tests/entry.test +++ b/tests/entry.test @@ -1621,13 +1621,26 @@ test entry-22.1 {lost namespaced textvar} { namespace eval test { variable foo {a b} } entry .e -textvariable ::test::foo namespace delete test - .e insert end "more stuff" - .e delete 5 end - catch {set ::test::foo} result - list [.e get] [.e cget -textvar] $result + catch {.e insert end "more stuff"} result1 + catch {.e delete 5 end} result2 + catch {set ::test::foo} result3 + list [.e get] [.e cget -textvar] $result1 $result2 $result3 } [list "a bmo" ::test::foo \ + {can't set "::test::foo": parent namespace doesn't exist} \ + {can't set "::test::foo": parent namespace doesn't exist} \ {can't read "::test::foo": no such variable}] +test entry-23.1 {error in trace proc attached to the textvariable} { + destroy .e + trace variable myvar w traceit + proc traceit args {error "Intentional error here!"} + entry .e -textvariable myvar + catch {.e insert end mystring} result1 + catch {.e delete 0} result2 + list $result1 $result2 +} [list {can't set "myvar": Intentional error here!} \ + {can't set "myvar": Intentional error here!}] + destroy .e # XXX Still need to write tests for EntryBlinkProc, EntryFocusProc, diff --git a/tests/spinbox.test b/tests/spinbox.test index 88e4d44..e8f027b 100644 --- a/tests/spinbox.test +++ b/tests/spinbox.test @@ -1568,6 +1568,21 @@ test spinbox-22.3 {spinbox config, -from changes SF bug 559078} { set val } {6} +test spinbox-23.1 {error in trace proc attached to the textvariable} { + destroy .s + trace variable myvar w traceit + proc traceit args {error "Intentional error here!"} + spinbox .s -textvariable myvar -from 1 -to 10 + catch {.s set mystring} result1 + catch {.s insert 0 mystring} result2 + catch {.s delete 0} result3 + catch {.s invoke buttonup} result4 + list $result1 $result2 $result3 $result4 +} [list {can't set "myvar": Intentional error here!} \ + {can't set "myvar": Intentional error here!} \ + {can't set "myvar": Intentional error here!} \ + {can't set "myvar": Intentional error here!}] + destroy .e catch {unset ::e ::vVals} -- cgit v0.12 From fbcd240c74caa743dc1fef3f144cd2154895befc Mon Sep 17 00:00:00 2001 From: fvogel Date: Fri, 11 Dec 2015 10:43:41 +0000 Subject: Reverted [30c7d14b21], but really use a spinbox and not an entry for the test... --- tests/spinbox.test | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/spinbox.test b/tests/spinbox.test index 88e4d44..0661635 100644 --- a/tests/spinbox.test +++ b/tests/spinbox.test @@ -1568,6 +1568,20 @@ test spinbox-22.3 {spinbox config, -from changes SF bug 559078} { set val } {6} +test spinbox-23.1 {selection present while disabled, bug 637828} { + destroy .e + spinbox .e + .e insert end 0123456789 + .e select from 3 + .e select to 6 + set out [.e selection present] + .e configure -state disabled + # still return 1 when disabled, because 'selection get' will work, + # but selection cannot be changed (new behavior since 8.4) + .e select to 9 + lappend out [.e selection present] [selection get] +} {1 1 345} + destroy .e catch {unset ::e ::vVals} -- cgit v0.12 From 7310553ebcfd9493614bbc345b30b0a747bef92d Mon Sep 17 00:00:00 2001 From: fvogel Date: Sat, 12 Dec 2015 14:42:51 +0000 Subject: Updated header comments of EntryValueChanged, InsertChars and DeleteChars since they now return a Tcl result --- generic/tkEntry.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/generic/tkEntry.c b/generic/tkEntry.c index a303bea..dd753f0 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -1999,7 +1999,8 @@ EntryComputeGeometry( * Add new characters to an entry widget. * * Results: - * None. + * A standard Tcl result. If an error occurred then an error message is + * left in the interp's result. * * Side effects: * New information gets added to entryPtr; it will be redisplayed soon, @@ -2099,7 +2100,8 @@ InsertChars( * Remove one or more characters from an entry widget. * * Results: - * None. + * A standard Tcl result. If an error occurred then an error message is + * left in the interp's result. * * Side effects: * Memory gets freed, the entry gets modified and (eventually) @@ -2216,7 +2218,8 @@ DeleteChars( * is one, and does other bookkeeping such as arranging for redisplay. * * Results: - * None. + * A standard Tcl result. If an error occurred then an error message is + * left in the interp's result. * * Side effects: * None. -- cgit v0.12 From 206fdec2adc973a08951dafa6b30a911e82918d5 Mon Sep 17 00:00:00 2001 From: fvogel Date: Sat, 12 Dec 2015 16:01:18 +0000 Subject: Fixed bug [793909] - Problem with nonexistent namespaces --- generic/tkEntry.c | 38 ++++++++++++++++++++++++++++++++------ tests/entry.test | 6 ++++++ tests/spinbox.test | 6 ++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/generic/tkEntry.c b/generic/tkEntry.c index dd753f0..338652b 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -1102,6 +1102,7 @@ ConfigureEntry( int valuesChanged = 0; /* lint initialization */ double oldFrom = 0.0; /* lint initialization */ double oldTo = 0.0; /* lint initialization */ + int code; /* * Eliminate any existing trace on a variable monitored by the entry. @@ -1297,9 +1298,6 @@ ConfigureEntry( /* * If the entry is tied to the value of a variable, create the variable if * it doesn't exist, and set the entry's value from the variable's value. - * No checking of the return value of EntryValueChanged is needed since it - * can only return TCL_ERROR if there is a trace on the textvariable and - * since any existing trace on it was eliminated above. */ if (entryPtr->textVarName != NULL) { @@ -1307,6 +1305,17 @@ ConfigureEntry( value = Tcl_GetVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY); if (value == NULL) { + + /* + * Since any trace on the textvariable was eliminated above, + * the only possible reason for EntryValueChanged to return + * an error is that the textvariable lives in a namespace + * that does not (yet) exist. Indeed, namespaces are not + * automatically created as needed. Don't trap this error + * here, better do it below when attempting to trace the + * variable. + */ + EntryValueChanged(entryPtr, NULL); } else { EntrySetValue(entryPtr, value); @@ -1325,7 +1334,13 @@ ConfigureEntry( */ Tcl_ListObjIndex(interp, sbPtr->listObj, 0, &objPtr); - EntryValueChanged(entryPtr, Tcl_GetString(objPtr)); + + /* + * No check for error return here as well, because any possible + * error will be trapped below when attempting tracing. + */ + + EntryValueChanged(entryPtr, Tcl_GetString(objPtr)); } else if ((sbPtr->valueStr == NULL) && !DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue) && (!DOUBLES_EQ(sbPtr->fromValue, oldFrom) @@ -1350,6 +1365,12 @@ ConfigureEntry( } } sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue); + + /* + * No check for error return here as well, because any possible + * error will be trapped below when attempting tracing. + */ + EntryValueChanged(entryPtr, sbPtr->formatBuf); } } @@ -1361,10 +1382,13 @@ ConfigureEntry( if ((entryPtr->textVarName != NULL) && !(entryPtr->flags & ENTRY_VAR_TRACED)) { - Tcl_TraceVar(interp, entryPtr->textVarName, + code = Tcl_TraceVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, EntryTextVarProc, (ClientData) entryPtr); - entryPtr->flags |= ENTRY_VAR_TRACED; + if (code != TCL_OK) { + return TCL_ERROR; + } + entryPtr->flags |= ENTRY_VAR_TRACED; } EntryWorldChanged((ClientData) entryPtr); @@ -2267,6 +2291,8 @@ EntryValueChanged( /* * An error may have happened when setting the textvariable in case there * is a trace on that variable and the trace proc triggered an error. + * Another possibility is that the textvariable is in a namespace that + * does not (yet) exist. * Signal this error. */ diff --git a/tests/entry.test b/tests/entry.test index da8f280..da3637d 100644 --- a/tests/entry.test +++ b/tests/entry.test @@ -1641,6 +1641,12 @@ test entry-23.1 {error in trace proc attached to the textvariable} { } [list {can't set "myvar": Intentional error here!} \ {can't set "myvar": Intentional error here!}] +test entry-24.1 {textvariable lives in a non-existing namespace} { + destroy .e + catch {entry .e -textvariable thisnsdoesntexist::myvar} result1 + set result1 +} {can't trace "thisnsdoesntexist::myvar": parent namespace doesn't exist} + destroy .e # XXX Still need to write tests for EntryBlinkProc, EntryFocusProc, diff --git a/tests/spinbox.test b/tests/spinbox.test index 5fba390..68c6fae 100644 --- a/tests/spinbox.test +++ b/tests/spinbox.test @@ -1599,6 +1599,12 @@ test spinbox-24.1 {error in trace proc attached to the textvariable} { {can't set "myvar": Intentional error here!} \ {can't set "myvar": Intentional error here!}] +test spinbox-25.1 {textvariable lives in a non-existing namespace} { + destroy .s + catch {spinbox .s -textvariable thisnsdoesntexist::myvar} result1 + set result1 +} {can't trace "thisnsdoesntexist::myvar": parent namespace doesn't exist} + catch {unset ::e ::vVals} ## -- cgit v0.12 From f4326da1c63ed8d0421a5d315255f2b2c402328e Mon Sep 17 00:00:00 2001 From: Kevin Walzer Date: Tue, 15 Dec 2015 02:53:13 +0000 Subject: Fix for some redraw issues on Tk-Cocoa on OS X 10.11; further refinement of memory management; thanks to Marc Culler for patches --- macosx/README | 61 +++++++++++++++++++++++++++++++++++++ macosx/tkMacOSXDraw.c | 10 ------- macosx/tkMacOSXEvent.c | 5 ---- macosx/tkMacOSXFont.c | 5 ++-- macosx/tkMacOSXInit.c | 67 ++++++++++++++++++++++++++++------------- macosx/tkMacOSXKeyEvent.c | 2 +- macosx/tkMacOSXMenu.c | 27 +++++------------ macosx/tkMacOSXNotify.c | 68 ++++++++++++++++++++++-------------------- macosx/tkMacOSXPrivate.h | 3 ++ macosx/tkMacOSXSubwindows.c | 11 +------ macosx/tkMacOSXWindowEvent.c | 34 ++++++++++++++------- macosx/tkMacOSXWm.c | 71 ++++++++++++++++++++++++++------------------ macosx/tkMacOSXXStubs.c | 3 +- 13 files changed, 225 insertions(+), 142 deletions(-) diff --git a/macosx/README b/macosx/README index b992a2e..7b17fb8 100644 --- a/macosx/README +++ b/macosx/README @@ -386,3 +386,64 @@ make overrides to the tk/macosx GNUmakefile, e.g. sudo make -C tk${ver}/macosx install \ TCL_FRAMEWORK_DIR=$HOME/Library/Frameworks TCLSH_DIR=$HOME/usr/bin The Makefile variables TCL_FRAMEWORK_DIR and TCLSH_DIR were added with Tk 8.4.3. + +4. About the event loop in Tk for Mac OSX +----------------------------------------- + +The main program in a typical OSX application looks like this (see *) + + void NSApplicationMain(int argc, char *argv[]) { + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"myMain" owner:NSApp]; + [NSApp run]; + } + +The run method implements the event loop for the application. There +are three key steps in the run method. First it calls +[NSApp finishLaunching], which creates the bouncing application icon +and does other mysterious things. Second it creates an +NSAutoreleasePool. Third, it starts an event loop which drains the +NSAutoreleasePool every time the queue is empty, and replaces the +drained pool with a new one. This third step is essential to +preventing memory leaks, since the internal methods of Appkit objects +all assume that an autorelease pool is in scope and will be drained +when the event processing cycle ends. + +Mac OSX Tk does not call the [NSApp run] method at all. Instead it +uses the event loop built in to Tk. So we must take care to replicate +the important features of the method ourselves. Here is how this +works in outline. + +We add a private NSAUtoreleasePool* property to our subclass of +NSApplication. (The subclass is called TKApplication but can be +referenced with the global variable NSApp). The TkpInit +function calls [NSApp _setup] which initializes this property by +creating an NSAutoreleasePool. A bit later on, TkpInit calls +[NSAPP _setupEventLoop] which in turn calls the +[NSApp finishLaunching] method. + +Each time that Tcl processes an event in its queue, it calls a +platform specific function which, in the case of Mac OSX, is named +TkMacOSXEventsCheckProc. In the unix implementations of Tk, including +the Mac OSX version, this function collects events from an "event +source", and transfers them to the Tcl event queue. In Mac OSX the +event source is the NSApplication event queue. Each NSEvent is +converted to a Tcl event which is added to the Tcl event queue. The +NSEvent is also passed to [NSApp sendevent], which sends the event on +to the application's NSWindows, which send it to their NSViews, etc. +Since the CheckProc function gets called for every Tk event, it is an +appropriate place to drain the main NSAutoreleasePool and replace it +with a new pool. This is done by calling the method +[NSApp _resetAutoreleasePool], where _resetAutoreleasePool is a method +which we define for the subclass TKApplication. + +One minor caveat is that there are several steps of the Tk +initialization which precede the call to TkpInit. Notably, the font +package is initialized first. Since there is no NSAUtoreleasePool in +scope prior to calling TkpInit, the functions called in these +preliminary stages need to create and drain their own +NSAutoreleasePools whenever they call methods of Appkit objects +(e.g. NSFont). + +* https://developer.apple.com/library/mac/documentation/Cocoa/\ +Reference/ApplicationKit/Classes/NSApplication_Class diff --git a/macosx/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c index b3c2499..f376591 100644 --- a/macosx/tkMacOSXDraw.c +++ b/macosx/tkMacOSXDraw.c @@ -138,7 +138,6 @@ BitmapRepFromDrawableRect( CGImageRef cg_image=NULL, sub_cg_image=NULL; NSBitmapImageRep *bitmap_rep=NULL; NSView *view=NULL; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; if ( mac_drawable->flags & TK_IS_PIXMAP ) { /* This means that the MacDrawable is functioning as a Tk Pixmap, so its view @@ -175,7 +174,6 @@ BitmapRepFromDrawableRect( } else { TkMacOSXDbgMsg("Invalid source drawable"); } - [pool drain]; return bitmap_rep; } @@ -1636,7 +1634,6 @@ TkMacOSXSetupDrawingContext( int dontDraw = 0, isWin = 0; TkMacOSXDrawingContext dc = {}; CGRect clipBounds; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; dc.clipRgn = TkMacOSXGetClipRgn(d); if (!dontDraw) { @@ -1767,7 +1764,6 @@ end: dc.clipRgn = NULL; } *dcPtr = dc; - [pool drain]; return !dontDraw; } @@ -1791,7 +1787,6 @@ void TkMacOSXRestoreDrawingContext( TkMacOSXDrawingContext *dcPtr) { - NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (dcPtr->context) { CGContextSynchronize(dcPtr->context); [[dcPtr->view window] setViewsNeedDisplay:YES]; @@ -1808,7 +1803,6 @@ TkMacOSXRestoreDrawingContext( #ifdef TK_MAC_DEBUG bzero(dcPtr, sizeof(TkMacOSXDrawingContext)); #endif /* TK_MAC_DEBUG */ - [pool drain]; } /* @@ -1834,7 +1828,6 @@ TkMacOSXGetClipRgn( { MacDrawable *macDraw = (MacDrawable *) drawable; HIShapeRef clipRgn = NULL; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (macDraw->winPtr && macDraw->flags & TK_CLIP_INVALID) { TkMacOSXUpdateClipRgn(macDraw->winPtr); @@ -1860,7 +1853,6 @@ TkMacOSXGetClipRgn( } else if (macDraw->visRgn) { clipRgn = HIShapeCreateCopy(macDraw->visRgn); } - [pool drain]; return clipRgn; } @@ -1913,7 +1905,6 @@ TkpClipDrawableToRect( { MacDrawable *macDraw = (MacDrawable *) d; NSView *view = TkMacOSXDrawableView(macDraw); - NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (macDraw->drawRgn) { CFRelease(macDraw->drawRgn); @@ -1947,7 +1938,6 @@ TkpClipDrawableToRect( macDraw->flags &= ~TK_FOCUSED_VIEW; } } - [pool drain]; } /* diff --git a/macosx/tkMacOSXEvent.c b/macosx/tkMacOSXEvent.c index 6685b80..3c59ac3 100644 --- a/macosx/tkMacOSXEvent.c +++ b/macosx/tkMacOSXEvent.c @@ -126,10 +126,6 @@ enum { MODULE_SCOPE void TkMacOSXFlushWindows(void) { - /* This can be called from outside the Appkit event loop, - * so it needs its own AutoreleasePool. - */ - NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSArray *macWindows = [NSApp orderedWindows]; for (NSWindow *w in macWindows) { @@ -137,7 +133,6 @@ TkMacOSXFlushWindows(void) [w flushWindow]; } } - [pool drain]; } /* diff --git a/macosx/tkMacOSXFont.c b/macosx/tkMacOSXFont.c index f1e01d2..f329071 100644 --- a/macosx/tkMacOSXFont.c +++ b/macosx/tkMacOSXFont.c @@ -210,6 +210,7 @@ FindNSFont( nsFont = [fm convertFont:nsFont toSize:size]; nsFont = [fm convertFont:nsFont toHaveTrait:traits]; } + [nsFont retain]; #undef defaultFont return nsFont; } @@ -371,6 +372,7 @@ TkpFontPkgInit( NSFont *nsFont; TkFontAttributes fa; NSMutableCharacterSet *cs; + /* Since we called before TkpInit, we need our own autorelease pool. */ NSAutoreleasePool *pool = [NSAutoreleasePool new]; /* force this for now */ @@ -530,7 +532,7 @@ TkpGetFontFromAttributes( nsFont = FindNSFont(faPtr->family, traits, weight, points, 1); } if (!nsFont) { - Tcl_Panic("Could not deternmine NSFont from TkFontAttributes"); + Tcl_Panic("Could not determine NSFont from TkFontAttributes"); } if (tkFontPtr == NULL) { fontPtr = (MacFont *) ckalloc(sizeof(MacFont)); @@ -675,7 +677,6 @@ TkpGetFontAttrsForChar( { MacFont *fontPtr = (MacFont *) tkfont; NSFont *nsFont = fontPtr->nsFont; - *faPtr = fontPtr->font.fa; if (nsFont && ![[nsFont coveredCharacterSet] characterIsMember:c]) { UTF16Char ch = c; diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c index 8e5479e..cb97f47 100644 --- a/macosx/tkMacOSXInit.c +++ b/macosx/tkMacOSXInit.c @@ -59,9 +59,19 @@ static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFSt @end @implementation TKApplication +#ifndef __clang__ +@synthesize poolProtected = _poolProtected; +#endif @end @implementation TKApplication(TKInit) +- (void) _resetAutoreleasePool +{ + if(![self poolProtected]) { + [_mainPool drain]; + _mainPool = [NSAutoreleasePool new]; + } +} #ifdef TK_MAC_DEBUG_NOTIFICATIONS - (void)_postedNotification:(NSNotification *)notification { TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); @@ -82,14 +92,17 @@ static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFSt #endif } - (void)_setupEventLoop { - - /*Remove private API calls here.*/ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; [self finishLaunching]; [self setWindowsNeedUpdate:YES]; + [pool drain]; } - (void)_setup:(Tcl_Interp *)interp { _eventInterp = interp; + _mainPool = nil; + [NSApp setPoolProtected:NO]; _defaultMainMenu = nil; + NSAutoreleasePool *pool = [NSAutoreleasePool new]; [self _setupMenus]; [self setDelegate:self]; #ifdef TK_MAC_DEBUG_NOTIFICATIONS @@ -98,9 +111,11 @@ static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFSt #endif [self _setupWindowNotifications]; [self _setupApplicationNotifications]; + [pool drain]; } - (NSString *)tkFrameworkImagePath:(NSString*)image { NSString *path = nil; + NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (tkLibPath[0] != '\0') { path = [[NSBundle bundleWithPath:[[NSString stringWithUTF8String: tkLibPath] stringByAppendingString:@"/../.."]] @@ -131,6 +146,8 @@ static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFSt } } #endif + [path retain]; + [pool drain]; return path; } @end @@ -157,6 +174,7 @@ static void SetApplicationIcon( ClientData clientData) { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSString *path = [NSApp tkFrameworkImagePath:@"Tk.icns"]; if (path) { NSImage *image = [[NSImage alloc] initWithContentsOfFile:path]; @@ -165,6 +183,7 @@ SetApplicationIcon( [image release]; } } + [pool drain]; } /* @@ -242,16 +261,19 @@ TkpInit( } #endif - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - [[NSUserDefaults standardUserDefaults] registerDefaults: - [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], - @"_NSCanWrapButtonTitles", - [NSNumber numberWithInt:-1], - @"NSStringDrawingTypesetterBehavior", - nil]]; - [TKApplication sharedApplication]; - [NSApp _setup:interp]; + { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + [[NSUserDefaults standardUserDefaults] registerDefaults: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], + @"_NSCanWrapButtonTitles", + [NSNumber numberWithInt:-1], + @"NSStringDrawingTypesetterBehavior", + nil]]; + [TKApplication sharedApplication]; + [pool drain]; + [NSApp _setup:interp]; + } /* Check whether we are a bundled executable: */ bundleRef = CFBundleGetMainBundle(); @@ -308,12 +330,15 @@ TkpInit( Tcl_DoWhenIdle(SetApplicationIcon, NULL); } - [NSApp _setupEventLoop]; - TkMacOSXInitAppleEvents(interp); - TkMacOSXUseAntialiasedText(interp, -1); - TkMacOSXInitCGDrawing(interp, TRUE, 0); - [pool drain]; - + { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + [NSApp _setupEventLoop]; + TkMacOSXInitAppleEvents(interp); + TkMacOSXUseAntialiasedText(interp, -1); + TkMacOSXInitCGDrawing(interp, TRUE, 0); + [pool drain]; + } + /* * FIXME: Close stdin & stdout for remote debugging otherwise we will * fight with gdb for stdin & stdout @@ -469,9 +494,8 @@ TkpDisplayWarning( MODULE_SCOPE void TkMacOSXDefaultStartupScript(void) { - CFBundleRef bundleRef; - - bundleRef = CFBundleGetMainBundle(); + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + CFBundleRef bundleRef = CFBundleGetMainBundle(); if (bundleRef != NULL) { CFURLRef appMainURL = CFBundleCopyResourceURL(bundleRef, @@ -495,6 +519,7 @@ TkMacOSXDefaultStartupScript(void) CFRelease(appMainURL); } } + [pool drain]; } /* diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index d21389b..da74e60 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -328,7 +328,7 @@ static unsigned isFunctionKey(unsigned int code); pt.y = caret_y; pt = [self convertPoint: pt toView: nil]; - pt = [[self window] convertBaseToScreen: pt]; + pt = [[self window] convertPointToScreen: pt]; pt.y -= caret_height; rect.origin = pt; diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index 3f4b3b5..8f20447 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -258,10 +258,10 @@ static int ModifierCharWidth(Tk_Font tkfont); if (menuPtr && mePtr) { Tcl_Interp *interp = menuPtr->interp; - /*Add time for errors to fire if necessary. This is sub-optimal but avoids issues with Tcl/Cocoa event loop integration.*/ + /*Add time for errors to fire if necessary. This is sub-optimal + *but avoids issues with Tcl/Cocoa event loop integration. + */ Tcl_Sleep(100); - - NSAutoreleasePool *pool = [NSAutoreleasePool new]; Tcl_Preserve(interp); Tcl_Preserve(menuPtr); @@ -274,7 +274,6 @@ static int ModifierCharWidth(Tk_Font tkfont); } Tcl_Release(menuPtr); Tcl_Release(interp); - [pool drain]; } } } @@ -377,13 +376,6 @@ static int ModifierCharWidth(Tk_Font tkfont); @implementation TKApplication(TKMenu) -- (void) safeSetMainMenu: (NSMenu *) menu -{ - NSAutoreleasePool* pool = [NSAutoreleasePool new]; - [self setMainMenu: menu]; - [pool drain]; -} - - (void) menuBeginTracking: (NSNotification *) notification { #ifdef TK_MAC_DEBUG_NOTIFICATIONS @@ -420,7 +412,7 @@ static int ModifierCharWidth(Tk_Font tkfont); if (!mePtr || !(mePtr->entryFlags & ENTRY_APPLE_MENU)) { applicationMenuItem = [NSMenuItem itemWithSubmenu: - [[_defaultApplicationMenu copy] autorelease]]; + [_defaultApplicationMenu copy]]; [menu insertItem:applicationMenuItem atIndex:0]; } [menu setSpecial:tkMainMenu]; @@ -428,7 +420,7 @@ static int ModifierCharWidth(Tk_Font tkfont); applicationMenu = (TKMenu *)[applicationMenuItem submenu]; if (![applicationMenu isSpecial:tkApplicationMenu]) { for (NSMenuItem *item in _defaultApplicationMenuItems) { - [applicationMenu addItem:[[item copy] autorelease]]; + [applicationMenu addItem:[item copy]]; } [applicationMenu setSpecial:tkApplicationMenu]; } @@ -438,15 +430,13 @@ static int ModifierCharWidth(Tk_Font tkfont); for (NSMenuItem *item in itemArray) { TkMenuEntry *mePtr = (TkMenuEntry *)[item tag]; TKMenu *submenu = (TKMenu *)[item submenu]; - if (mePtr && submenu) { if ((mePtr->entryFlags & ENTRY_WINDOWS_MENU) && ![submenu isSpecial:tkWindowsMenu]) { NSInteger index = 0; for (NSMenuItem *i in _defaultWindowsMenuItems) { - [submenu insertItem:[[i copy] autorelease] atIndex: - index++]; + [submenu insertItem:[i copy] atIndex:index++]; } [self setWindowsMenu:submenu]; [submenu setSpecial:tkWindowsMenu]; @@ -455,8 +445,7 @@ static int ModifierCharWidth(Tk_Font tkfont); NSInteger index = 0; for (NSMenuItem *i in _defaultHelpMenuItems) { - [submenu insertItem:[[i copy] autorelease] atIndex: - index++]; + [submenu insertItem:[i copy] atIndex:index++]; } [submenu setSpecial:tkHelpMenu]; } @@ -475,7 +464,7 @@ static int ModifierCharWidth(Tk_Font tkfont); [servicesMenuItem setSubmenu:_servicesMenu]; } [self setAppleMenu:applicationMenu]; - [self safeSetMainMenu:menu]; + [self setMainMenu:menu]; } @end diff --git a/macosx/tkMacOSXNotify.c b/macosx/tkMacOSXNotify.c index c703297..0737d74 100644 --- a/macosx/tkMacOSXNotify.c +++ b/macosx/tkMacOSXNotify.c @@ -49,7 +49,7 @@ static void TkMacOSXEventsCheckProc(ClientData clientData, int flags); @end @implementation TKApplication(TKNotify) -/* Call super then redisplay all of our windows. */ +/* Display all windows each time an event is removed from the queue.*/ - (NSEvent *)nextEventMatchingMask:(NSUInteger)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag { @@ -57,9 +57,9 @@ static void TkMacOSXEventsCheckProc(ClientData clientData, int flags); untilDate:expiration inMode:mode dequeue:deqFlag]; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; + /* Retain this event for later use. Must be released.*/ + [event retain]; [NSApp makeWindowsPerform:@selector(tkDisplayIfNeeded) inOrder:NO]; - [pool drain]; return event; } @@ -67,10 +67,8 @@ static void TkMacOSXEventsCheckProc(ClientData clientData, int flags); * Call super then check the pasteboard. */ - (void)sendEvent:(NSEvent *)theEvent { - NSAutoreleasePool *pool = [NSAutoreleasePool new]; [super sendEvent:theEvent]; [NSApp tkCheckPasteboard]; - [pool drain]; } @end @@ -191,7 +189,7 @@ TkMacOSXNotifyExitHandler( * TkMacOSXEventsSetupProc -- * * This procedure implements the setup part of the MacOSX event - * source. It is invoked by Tcl_DoOneEvent before calling + * source. It is invoked by Tcl_DoOneEvent before calling * TkMacOSXEventsProc to process all queued NSEvents. In our * case, all we need to do is to set the Tcl MaxBlockTime to * 0 before starting the loop to process all queued NSEvents. @@ -200,7 +198,7 @@ TkMacOSXNotifyExitHandler( * None. * * Side effects: - * + * * If NSEvents are queued, then the maximum block time will be set * to 0 to ensure that control returns immediately to Tcl. * @@ -212,19 +210,21 @@ TkMacOSXEventsSetupProc( ClientData clientData, int flags) { - if (flags & TCL_WINDOW_EVENTS && - ![[NSRunLoop currentRunLoop] currentMode]) { + NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; + /* runloopMode will be nil if we are in the Tcl event loop. */ + if (flags & TCL_WINDOW_EVENTS && !runloopMode) { static Tcl_Time zeroBlockTime = { 0, 0 }; - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - /* Call this with dequeue=NO -- just checking if the queue is empty. */ + /* Call this with dequeue=NO -- just checking if the queue is empty. */ NSEvent *currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:GetRunLoopMode(TkMacOSXGetModalSession()) - dequeue:NO]; - if (currentEvent && currentEvent.type > 0) { - Tcl_SetMaxBlockTime(&zeroBlockTime); + untilDate:[NSDate distantPast] + inMode:GetRunLoopMode(TkMacOSXGetModalSession()) + dequeue:NO]; + if (currentEvent) { + if (currentEvent.type > 0) { + Tcl_SetMaxBlockTime(&zeroBlockTime); + } + [currentEvent release]; } - [pool drain]; } } @@ -251,13 +251,14 @@ TkMacOSXEventsCheckProc( int flags) { NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; + /* runloopMode will be nil if we are in the Tcl event loop. */ if (flags & TCL_WINDOW_EVENTS && !runloopMode) { - NSEvent *currentEvent = nil; NSEvent *testEvent = nil; NSModalSession modalSession; do { + [NSApp _resetAutoreleasePool]; modalSession = TkMacOSXGetModalSession(); testEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] @@ -272,25 +273,26 @@ TkMacOSXEventsCheckProc( untilDate:[NSDate distantPast] inMode:GetRunLoopMode(modalSession) dequeue:YES]; - if (!currentEvent) { - break; /* No events are available. */ - } - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - /* Generate Xevents. */ - int oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); - NSEvent *processedEvent = [NSApp tkProcessEvent:currentEvent]; - Tcl_SetServiceMode(oldServiceMode); - if (processedEvent) { /* Should always be non-NULL. */ + if (currentEvent) { + [NSApp _resetAutoreleasePool]; + /* Generate Xevents. */ + int oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); + NSEvent *processedEvent = [NSApp tkProcessEvent:currentEvent]; + Tcl_SetServiceMode(oldServiceMode); + if (processedEvent) { /* Should always be non-NULL. */ #ifdef TK_MAC_DEBUG_EVENTS - TKLog(@" event: %@", currentEvent); + TKLog(@" event: %@", currentEvent); #endif - if (modalSession) { - [NSApp _modalSession:modalSession sendEvent:currentEvent]; - } else { - [NSApp sendEvent:currentEvent]; + if (modalSession) { + [NSApp _modalSession:modalSession sendEvent:currentEvent]; + } else { + [NSApp sendEvent:currentEvent]; + } } + [currentEvent release]; + } else { + break; } - [pool drain]; } while (1); } } diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index 4891b32..e635020 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -273,10 +273,13 @@ VISIBILITY_HIDDEN NSArray *_defaultApplicationMenuItems, *_defaultWindowsMenuItems; NSArray *_defaultHelpMenuItems; NSWindow *_windowWithMouse; + NSAutoreleasePool *_mainPool; } +@property BOOL poolProtected; @end @interface TKApplication(TKInit) - (NSString *)tkFrameworkImagePath:(NSString*)image; +- (void)_resetAutoreleasePool; @end @interface TKApplication(TKEvent) - (NSEvent *)tkProcessEvent:(NSEvent *)theEvent; diff --git a/macosx/tkMacOSXSubwindows.c b/macosx/tkMacOSXSubwindows.c index b985e5d..c1f16f3 100644 --- a/macosx/tkMacOSXSubwindows.c +++ b/macosx/tkMacOSXSubwindows.c @@ -131,11 +131,6 @@ XMapWindow( { MacDrawable *macWin = (MacDrawable *) window; XEvent event; - /* - * This function can be called from outside the AppKit event - * loop, so it needs its own AutoreleasePool. - */ - NSAutoreleasePool* pool = [NSAutoreleasePool new]; /* * Under certain situations it's possible for this function to be called @@ -158,6 +153,7 @@ XMapWindow( if ( [win canBecomeKeyWindow] ) { [win makeKeyAndOrderFront:NSApp]; } + /* Why do we need this? (It is used by Carbon)*/ [win windowRef]; TkMacOSXApplyWindowAttributes(macWin->winPtr, win); } @@ -194,7 +190,6 @@ XMapWindow( event.xvisibility.type = VisibilityNotify; event.xvisibility.state = VisibilityUnobscured; NotifyVisibility(macWin->winPtr, &event); - [pool drain]; } /* @@ -318,7 +313,6 @@ XResizeWindow( unsigned int height) { MacDrawable *macWin = (MacDrawable *) window; - NSAutoreleasePool *pool= [NSAutoreleasePool new]; display->request++; if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { NSWindow *w = macWin->winPtr->wmInfoPtr->window; @@ -333,7 +327,6 @@ XResizeWindow( } else { MoveResizeWindow(macWin); } - [pool drain]; } /* @@ -366,7 +359,6 @@ XMoveResizeWindow( display->request++; if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { NSWindow *w = macWin->winPtr->wmInfoPtr->window; - if (w) { NSRect r = NSMakeRect(x + macWin->winPtr->wmInfoPtr->xInParent, tkMacOSXZeroScreenHeight - (y + @@ -407,7 +399,6 @@ XMoveWindow( display->request++; if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { NSWindow *w = macWin->winPtr->wmInfoPtr->window; - if (w) { [w setFrameTopLeftPoint:NSMakePoint(x, tkMacOSXZeroScreenHeight - y)]; } diff --git a/macosx/tkMacOSXWindowEvent.c b/macosx/tkMacOSXWindowEvent.c index 0b32e7e..91cc348 100644 --- a/macosx/tkMacOSXWindowEvent.c +++ b/macosx/tkMacOSXWindowEvent.c @@ -808,7 +808,7 @@ ConfigureRestrictProc( { const NSRect *rectsBeingDrawn; NSInteger rectsBeingDrawnCount; - + [self getRectsBeingDrawn:&rectsBeingDrawn count:&rectsBeingDrawnCount]; #ifdef TK_MAC_DEBUG_DRAWING @@ -844,7 +844,8 @@ ConfigureRestrictProc( -(void) setFrameSize: (NSSize)newsize { - if ( [self inLiveResize] ) { + [super setFrameSize: newsize]; + if ([self inLiveResize]) { NSWindow *w = [self window]; TkWindow *winPtr = TkMacOSXGetTkWindow(w); Tk_Window tkwin = (Tk_Window) winPtr; @@ -853,17 +854,29 @@ ConfigureRestrictProc( ClientData oldArg; Tk_RestrictProc *oldProc; - /* Resize the NSView */ - [super setFrameSize: newsize]; - - /* Disable drawing until the window has been completely configured.*/ + /* This can be called from outside the Tk event loop. + * Since it calls Tcl_DoOneEvent, we need to make sure we + * don't clobber the AutoreleasePool set up by the caller. + */ + [NSApp setPoolProtected:YES]; + + /* + * Try to prevent flickers and flashes. + * + * This stops the flickers, but on OSX 10.11 flashes still occur when + * the width of the window is 16, 32, 48, 64, 80, 96, 112, 256, 512, + * 768, ... + */ + [w disableFlushWindow]; + + /* Disable Tk drawing until the window has been completely configured.*/ TkMacOSXSetDrawingEnabled(winPtr, 0); /* Generate and handle a ConfigureNotify event for the new size.*/ TkGenWMConfigureEvent(tkwin, Tk_X(tkwin), Tk_Y(tkwin), width, height, TK_SIZE_CHANGED | TK_MACOSX_HANDLE_EVENT_IMMEDIATELY); oldProc = Tk_RestrictEvents(ConfigureRestrictProc, NULL, &oldArg); - while ( Tk_DoOneEvent(TK_X_EVENTS|TK_DONT_WAIT) ) {} + while (Tk_DoOneEvent(TK_X_EVENTS|TK_DONT_WAIT)) {} Tk_RestrictEvents(oldProc, oldArg, &oldArg); /* Now that Tk has configured all subwindows we can create the clip regions. */ @@ -875,9 +888,10 @@ ConfigureRestrictProc( HIRect bounds = NSRectToCGRect([self bounds]); HIShapeRef shape = HIShapeCreateWithRect(&bounds); [self generateExposeEvents: shape]; - while ( Tk_DoOneEvent(TK_ALL_EVENTS|TK_DONT_WAIT) ) {} - } else { - [super setFrameSize: newsize]; + while (Tk_DoOneEvent(TK_ALL_EVENTS|TK_DONT_WAIT)) {} + [w enableFlushWindow]; + [w flushWindowIfNeeded]; + [NSApp setPoolProtected:NO]; } } diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 4ce2206..50cac20 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -408,18 +408,23 @@ static void RemapWindows(TkWindow *winPtr, if (title == nil) { title = "unnamed window"; } - printf("Retained <%s>. Count is: %lu\n", title, [self retainCount]); + if (DEBUG_ZOMBIES > 1){ + printf("Retained <%s>. Count is: %lu\n", title, [self retainCount]); + } return result; } - (id) autorelease { + static int xcount = 0; id result = [super autorelease]; const char *title = [[self title] UTF8String]; if (title == nil) { title = "unnamed window"; } - printf("Autoreleased <%s>. Count is %lu\n", title, [self retainCount]); + if (DEBUG_ZOMBIES > 1){ + printf("Autoreleased <%s>. Count is %lu\n", title, [self retainCount]); + } return result; } @@ -428,9 +433,24 @@ static void RemapWindows(TkWindow *winPtr, if (title == nil) { title = "unnamed window"; } - printf("Releasing <%s>. Count is %lu\n", title, [self retainCount]); + if (DEBUG_ZOMBIES > 1){ + printf("Releasing <%s>. Count is %lu\n", title, [self retainCount]); + } [super release]; } + +- (void) dealloc { + const char *title = [[self title] UTF8String]; + if (title == nil) { + title = "unnamed window"; + } + if (DEBUG_ZOMBIES > 0){ + printf(">>>> Freeing <%s>. Count is %lu\n", title, [self retainCount]); + } + [super dealloc]; +} + + #endif @end @@ -544,7 +564,6 @@ FrontWindowAtPoint( int x, int y) { NSPoint p = NSMakePoint(x, tkMacOSXZeroScreenHeight - y); - NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSArray *windows = [NSApp orderedWindows]; TkWindow *front = NULL; @@ -554,7 +573,6 @@ FrontWindowAtPoint( break; } } - [pool drain]; return front; } @@ -860,7 +878,6 @@ TkWmDeadWindow( NSWindow *window = wmPtr->window; if (window && !Tk_IsEmbedded(winPtr) ) { - NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSWindow *parent = [window parentWindow]; if (parent) { [parent removeChildWindow:window]; @@ -870,29 +887,33 @@ TkWmDeadWindow( if (winPtr->window) { ((MacDrawable *) winPtr->window)->view = nil; } -#if DEBUG_ZOMBIES +#if DEBUG_ZOMBIES > 0 { const char *title = [[window title] UTF8String]; if (title == nil) { title = "unnamed window"; } - printf("Closing <%s>. Count is: %lu\n", title, [window retainCount]); + printf(">>>> Closing <%s>. Count is: %lu\n", title, [window retainCount]); } #endif [window release]; wmPtr->window = NULL; - /* Activate the highest window left on the screen. */ - NSArray *windows = [NSApp orderedWindows]; - if ( [windows count] > 0 ) { - NSWindow *front = [windows objectAtIndex:0]; - if ( front && [front canBecomeKeyWindow] ) { - [front makeKeyAndOrderFront:NSApp]; - } - } - [pool drain]; - } + + /* Activate the highest window left on the screen. */ + NSArray *windows = [NSApp orderedWindows]; + if ( [windows count] > 0 ) { + NSWindow *front = [windows objectAtIndex:0]; + if ( front && [front canBecomeKeyWindow] ) { + [front makeKeyAndOrderFront:NSApp]; + } + } +#if DEBUG_ZOMBIES > 0 + fprintf(stderr, "================= Pool dump ===================\n"); + [NSAutoreleasePool showPools]; +#endif ckfree((char *)wmPtr); winPtr->wmInfoPtr = NULL; + } } /* @@ -5478,9 +5499,6 @@ TkMacOSXMakeRealWindowExist( * TODO: Here we should handle out of process embedding. */ } - - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - WindowClass macClass = wmPtr->macClass; wmPtr->attributes &= (tkAlwaysValidAttributes | macClassAttrs[macClass].validAttrs); @@ -5514,10 +5532,10 @@ TkMacOSXMakeRealWindowExist( NSWindow *window = [[winClass alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:YES]; if (!window) { - Tcl_Panic("couldn't allocate new Mac window"); + Tcl_Panic("couldn't allocate new Mac window"); } TKContentView *contentView = [[TKContentView alloc] - initWithFrame:NSZeroRect]; + initWithFrame:NSZeroRect]; [window setContentView:contentView]; [contentView release]; [window setDelegate:NSApp]; @@ -5552,7 +5570,7 @@ TkMacOSXMakeRealWindowExist( [window setDocumentEdited:NO]; wmPtr->window = window; - macWin->view = contentView; + macWin->view = window.contentView; TkMacOSXApplyWindowAttributes(winPtr, window); NSRect geometry = InitialWindowBounds(winPtr, window); @@ -5561,11 +5579,8 @@ TkMacOSXMakeRealWindowExist( geometry.origin.y = tkMacOSXZeroScreenHeight - (geometry.origin.y + geometry.size.height); [window setFrame:geometry display:NO]; - TkMacOSXRegisterOffScreenWindow((Window) macWin, window); macWin->flags |= TK_HOST_EXISTS; - - [pool drain]; } /* @@ -5958,7 +5973,6 @@ TkpChangeFocus( if (Tk_IsTopLevel(winPtr) && !Tk_IsEmbedded(winPtr) ){ NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); - NSAutoreleasePool *pool = [NSAutoreleasePool new]; TkWmRestackToplevel(winPtr, Above, NULL); if (force ) { [NSApp activateIgnoringOtherApps:YES]; @@ -5966,7 +5980,6 @@ TkpChangeFocus( if ( win && [win canBecomeKeyWindow] ) { [win makeKeyAndOrderFront:NSApp]; } - [pool drain]; } /* diff --git a/macosx/tkMacOSXXStubs.c b/macosx/tkMacOSXXStubs.c index 4bdd1c4..a16daa8 100644 --- a/macosx/tkMacOSXXStubs.c +++ b/macosx/tkMacOSXXStubs.c @@ -142,6 +142,7 @@ TkpOpenDisplay( static NSRect maxBounds = {{0, 0}, {0, 0}}; static char vendor[25] = ""; NSArray *cgVers; + NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (gMacDisplay != NULL) { if (strcmp(gMacDisplay->display->display_name, display_name) == 0) { @@ -151,8 +152,6 @@ TkpOpenDisplay( } } - NSAutoreleasePool *pool = [NSAutoreleasePool new]; - display = (Display *) ckalloc(sizeof(Display)); screen = (Screen *) ckalloc(sizeof(Screen)); bzero(display, sizeof(Display)); -- cgit v0.12