summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tkText.c58
-rw-r--r--generic/tkText.h2
-rw-r--r--generic/tkTextDisp.c96
-rw-r--r--tests/text.test146
-rw-r--r--tests/textDisp.test41
5 files changed, 309 insertions, 34 deletions
diff --git a/generic/tkText.c b/generic/tkText.c
index 6e982b0..62de1af 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -689,15 +689,16 @@ TextWidgetObjCmd(
static const char *optionStrings[] = {
"bbox", "cget", "compare", "configure", "count", "debug", "delete",
"dlineinfo", "dump", "edit", "get", "image", "index", "insert",
- "mark", "peer", "replace", "scan", "search", "see", "tag", "window",
- "xview", "yview", NULL
+ "mark", "peer", "pendingsync", "replace", "scan", "search",
+ "see", "sync", "tag", "window", "xview", "yview", NULL
};
enum options {
TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT,
TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT,
TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK,
- TEXT_PEER, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE,
- TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
+ TEXT_PEER, TEXT_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN,
+ TEXT_SEARCH, TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WINDOW,
+ TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
@@ -1372,6 +1373,18 @@ TextWidgetObjCmd(
case TEXT_PEER:
result = TextPeerCmd(textPtr, interp, objc, objv);
break;
+ case TEXT_PENDINGSYNC: {
+ int number;
+
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ number = TkTextPendingsync(textPtr);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(number));
+ break;
+ }
case TEXT_REPLACE: {
const TkTextIndex *indexFromPtr, *indexToPtr;
@@ -1485,6 +1498,39 @@ TextWidgetObjCmd(
case TEXT_SEE:
result = TkTextSeeCmd(textPtr, interp, objc, objv);
break;
+ case TEXT_SYNC: {
+ if (objc == 4) {
+ Tcl_Obj *cmd = objv[3];
+ const char *option = Tcl_GetString(objv[2]);
+ if (strncmp(option, "-command", objv[2]->length)) {
+ Tcl_AppendResult(interp, "wrong option \"", option, "\": should be \"-command\"", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ Tcl_IncrRefCount(cmd);
+ if (TkTextPendingsync(textPtr)) {
+ if (textPtr->afterSyncCmd) {
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ }
+ textPtr->afterSyncCmd = cmd;
+ } else {
+ result = Tcl_EvalObjEx(interp, cmd, TCL_EVAL_GLOBAL);
+ Tcl_DecrRefCount(cmd);
+ }
+ break;
+ } else if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?-command command?");
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (textPtr->afterSyncCmd) {
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ }
+ textPtr->afterSyncCmd = NULL;
+ TkTextUpdateLineMetrics(textPtr, 1,
+ TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);
+ break;
+ }
case TEXT_TAG:
result = TkTextTagCmd(textPtr, interp, objc, objv);
break;
@@ -1970,6 +2016,10 @@ DestroyText(
textPtr->tkwin = NULL;
textPtr->refCount--;
Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd);
+ if (textPtr->afterSyncCmd != 0){
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = 0;
+ }
if (textPtr->refCount == 0) {
ckfree((char *) textPtr);
}
diff --git a/generic/tkText.h b/generic/tkText.h
index 6f5f153..1c4be68 100644
--- a/generic/tkText.h
+++ b/generic/tkText.h
@@ -782,6 +782,7 @@ typedef struct TkText {
* statements. */
int autoSeparators; /* Non-zero means the separators will be
* inserted automatically. */
+ Tcl_Obj *afterSyncCmd; /* Command to be executed when lines are up to date */
} TkText;
/*
@@ -1124,6 +1125,7 @@ MODULE_SCOPE int TkTextMarkNameToIndex(TkText *textPtr,
MODULE_SCOPE void TkTextMarkSegToIndex(TkText *textPtr,
TkTextSegment *markPtr, TkTextIndex *indexPtr);
MODULE_SCOPE void TkTextEventuallyRepick(TkText *textPtr);
+MODULE_SCOPE int TkTextPendingsync(TkText *textPtr);
MODULE_SCOPE void TkTextPickCurrent(TkText *textPtr, XEvent *eventPtr);
MODULE_SCOPE void TkTextPixelIndex(TkText *textPtr, int x, int y,
TkTextIndex *indexPtr, int *nearest);
diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c
index 6036222..d8a17a9 100644
--- a/generic/tkTextDisp.c
+++ b/generic/tkTextDisp.c
@@ -590,6 +590,7 @@ static int TextGetScrollInfoObj(Tcl_Interp *interp,
Tcl_Obj *CONST objv[], double *dblPtr,
int *intPtr);
static void AsyncUpdateLineMetrics(ClientData clientData);
+static void GenerateWidgetViewSyncEvent(TkText *textPtr, Bool InSync);
static void AsyncUpdateYScrollbar(ClientData clientData);
static int IsStartOfNotMergedLine(TkText *textPtr,
CONST TkTextIndex *indexPtr);
@@ -2920,6 +2921,8 @@ AsyncUpdateLineMetrics(
lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
dInfoPtr->lastMetricUpdateLine, 256);
+ dInfoPtr->currentMetricUpdateLine = lineNum;
+
if (tkTextDebug) {
char buffer[2 * TCL_INTEGER_SPACE + 1];
@@ -2937,16 +2940,37 @@ AsyncUpdateLineMetrics(
/*
* We have looped over all lines, so we're done. We must release our
* refCount on the widget (the timer token was already set to NULL
- * above).
+ * above). If there is a registered aftersync command, run that first.
*/
+ if (textPtr->afterSyncCmd != NULL) {
+ int code;
+ Tcl_Preserve((ClientData)textPtr->interp);
+ code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd, TCL_EVAL_GLOBAL);
+ if (code != TCL_OK && code != TCL_CONTINUE
+ && code != TCL_BREAK) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
+ Tcl_BackgroundError(textPtr->interp);
+ }
+ Tcl_Release((ClientData)textPtr->interp);
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = 0;
+ }
+
+ /*
+ * Fire the <<WidgetViewSync>> event since the widget view is in sync
+ * with its internal data (actually it will be after the next trip
+ * through the event loop, because the widget redraws at idle-time).
+ */
+
+ GenerateWidgetViewSyncEvent(textPtr, 1);
+
textPtr->refCount--;
if (textPtr->refCount == 0) {
ckfree((char *) textPtr);
}
return;
}
- dInfoPtr->currentMetricUpdateLine = lineNum;
/*
* Re-arm the timer. We already have a refCount on the text widget so no
@@ -2960,6 +2984,45 @@ AsyncUpdateLineMetrics(
/*
*----------------------------------------------------------------------
*
+ * GenerateWidgetViewSyncEvent --
+ *
+ * Send the <<WidgetViewSync>> event related to the text widget
+ * line metrics asynchronous update.
+ * This is equivalent to:
+ * event generate $textWidget <<WidgetViewSync>> -detail $s
+ * where $s is the sync status: true (when the widget view is in
+ * sync with its internal data) or false (when it is not).
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * If corresponding bindings are present, they will trigger.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+GenerateWidgetViewSyncEvent(
+ TkText *textPtr, /* Information about text widget. */
+ Bool InSync) /* True if in sync, false otherwise */
+{
+ union {XEvent general; XVirtualEvent virtual;} event;
+
+ memset(&event, 0, sizeof(event));
+ event.general.xany.type = VirtualEvent;
+ event.general.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
+ event.general.xany.send_event = False;
+ event.general.xany.window = Tk_WindowId(textPtr->tkwin);
+ event.general.xany.display = Tk_Display(textPtr->tkwin);
+ event.virtual.name = Tk_GetUid("WidgetViewSync");
+ event.virtual.user_data = Tcl_NewBooleanObj(InSync);
+ Tk_HandleEvent(&event.general);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkTextUpdateLineMetrics --
*
* This function updates the pixel height calculations of a range of
@@ -3336,6 +3399,7 @@ TextInvalidateLineMetrics(
textPtr->refCount++;
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
AsyncUpdateLineMetrics, (ClientData) textPtr);
+ GenerateWidgetViewSyncEvent(textPtr, 0);
}
}
@@ -5040,6 +5104,7 @@ TkTextRelayoutWindow(
textPtr->refCount++;
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
AsyncUpdateLineMetrics, (ClientData) textPtr);
+ GenerateWidgetViewSyncEvent(textPtr, 0);
}
}
}
@@ -6034,6 +6099,33 @@ TkTextYviewCmd(
/*
*--------------------------------------------------------------
*
+ * TkTextPendingsync --
+ *
+ * This function computes how many lines are not up-to-date regarding
+ * asynchronous height calculations.
+ *
+ * Results:
+ * Returns a positive integer corresponding to the number of lines for
+ * which the height is outdated.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+int
+TkTextPendingsync(
+ TkText *textPtr) /* Information about text widget. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ return (dInfoPtr->lastMetricUpdateLine - dInfoPtr->currentMetricUpdateLine);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
* TkTextScanCmd --
*
* This function is invoked to process the "scan" option for the widget
diff --git a/tests/text.test b/tests/text.test
index 7c1731d..dc44293 100644
--- a/tests/text.test
+++ b/tests/text.test
@@ -153,7 +153,7 @@ test text-3.1 {TextWidgetCmd procedure, basics} {
} {1 {wrong # args: should be ".t option ?arg arg ...?"}}
test text-3.2 {TextWidgetCmd procedure} {
list [catch {.t gorp 1.0 z 1.2} msg] $msg
-} {1 {bad option "gorp": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}}
+} {1 {bad option "gorp": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}}
test text-4.1 {TextWidgetCmd procedure, "bbox" option} {
list [catch {.t bbox} msg] $msg
@@ -221,7 +221,7 @@ test text-6.13 {TextWidgetCmd procedure, "compare" option} {
} {1 {bad comparison operator "z": must be <, <=, ==, >=, >, or !=}}
test text-6.14 {TextWidgetCmd procedure, "compare" option} {
list [catch {.t co 1.0 z 1.2} msg] $msg
-} {1 {ambiguous option "co": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}}
+} {1 {ambiguous option "co": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}}
# "configure" option is already covered above
@@ -230,7 +230,7 @@ test text-7.1 {TextWidgetCmd procedure, "debug" option} {
} {1 {wrong # args: should be ".t debug boolean"}}
test text-7.2 {TextWidgetCmd procedure, "debug" option} {
list [catch {.t de 0 1} msg] $msg
-} {1 {ambiguous option "de": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}}
+} {1 {ambiguous option "de": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}}
test text-7.3 {TextWidgetCmd procedure, "debug" option} {
.t debug true
.t deb
@@ -745,10 +745,7 @@ test text-9.2.47 {TextWidgetCmd procedure, "count" option} -setup {
.t tag configure hidden -elide true
.t tag add hidden 5.7 11.0
update
- # next line to be fully sure that asynchronous line heights calculation is
- # up-to-date otherwise this test may fail (depending on the computer
- # performance), especially when the . toplevel has small height
- .t count -update -ypixels 1.0 end
+ .t sync
set y1 [lindex [.t yview] 1]
.t count -displaylines 5.0 11.0
set y2 [lindex [.t yview] 1]
@@ -901,7 +898,7 @@ test text-10.2 {TextWidgetCmd procedure, "index" option} {
} {1 {wrong # args: should be ".t index index"}}
test text-10.3 {TextWidgetCmd procedure, "index" option} {
list [catch {.t in a b} msg] $msg
-} {1 {ambiguous option "in": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, replace, scan, search, see, tag, window, xview, or yview}}
+} {1 {ambiguous option "in": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}}
test text-10.4 {TextWidgetCmd procedure, "index" option} {
list [catch {.t index @xyz} msg] $msg
} {1 {bad text index "@xyz"}}
@@ -960,7 +957,136 @@ test text-11.10 {TextWidgetCmd procedure, "insert" option} {
list [.t get 1.0 1.end] [.t tag ranges bold] [.t tag ranges silly]
} {{First second} {1.0 1.5} {1.5 1.12}}
-# Edit, mark, scan, search, see, tag, window, xview, and yview actions are tested elsewhere.
+test text-11a.1 {TextWidgetCmd procedure, "sync" option} -setup {
+ destroy .yt
+} -body {
+ text .yt
+ list [catch {.yt sync mytext} msg] $msg
+} -cleanup {
+ destroy .yt
+} -result {1 {wrong # args: should be ".yt sync ?-command command?"}}
+test text-11a.2 {TextWidgetCmd procedure, "sync" option with -command} -setup {
+ destroy .yt
+} -body {
+ text .yt
+ list [catch {.yt sync -comx foo} msg] $msg
+} -cleanup {
+ destroy .yt
+} -result {1 {wrong option "-comx": should be "-command"}}
+test text-11a.3 {TextWidgetCmd procedure, "sync" option} -setup {
+ destroy .top.yt .top
+} -body {
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 30} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ # wait for end of line metrics calculation to get correct $fraction1
+ # as a reference
+ .top.yt sync
+ .top.yt yview moveto 1
+ set fraction1 [lindex [.top.yt yview] 0]
+ set res [expr {$fraction1 > 0}]
+ # first case: do not wait for completion of line metrics calculation
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+ # second case: wait for completion of line metrics calculation
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ .top.yt sync
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 0 1}
+test text-11a.4 {TextWidgetCmd procedure, "sync" option with -command} -setup {
+ destroy .yt
+} -body {
+ set ::x 0
+ pack [text .yt] -expand 1 -fill both
+ .yt sync -command [list set ::x 1]
+ set ::x
+} -cleanup {
+ destroy .yt
+} -result {1}
+
+test text-11a.11 {TextWidgetCmd procedure, "pendingsync" option} -setup {
+ destroy .yt
+} -body {
+ text .yt
+ list [catch {.yt pendingsync mytext} msg] $msg
+} -cleanup {
+ destroy .yt
+} -result {1 {wrong # args: should be ".yt pendingsync"}}
+test text-11a.12 {TextWidgetCmd procedure, "pendingsync" option} -setup {
+ destroy .top.yt .top
+} -body {
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 300} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ update
+ # wait for end of line metrics calculation to get correct $fraction1
+ # as a reference
+ while {[.top.yt pendingsync]} {update}
+ .top.yt yview moveto 1
+ set fraction1 [lindex [.top.yt yview] 0]
+ set res [expr {$fraction1 > 0}]
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ # ensure the test is relevant
+ lappend res [expr {[.top.yt pendingsync] > 0}]
+ # asynchronously wait for completion of line metrics calculation
+ while {[.top.yt pendingsync]} {update}
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 1 1}
+
+test text-11a.21 {"<<WidgetViewSync>>" event} -setup {
+ destroy .top.yt .top
+} -body {
+ toplevel .top
+ pack [text .top.yt]
+ set content {}
+ for {set i 1} {$i < 300} {incr i} {
+ append content [string repeat "$i " 15] \n
+ }
+ .top.yt insert 1.0 $content
+ update
+ bind .top.yt <<WidgetViewSync>> { if {%d} {set yud(%W) 1} }
+ # wait for end of line metrics calculation to get correct $fraction1
+ # as a reference
+ if {[.top.yt pendingsync]} {vwait yud(.top.yt)}
+ .top.yt yview moveto 1
+ set fraction1 [lindex [.top.yt yview] 0]
+ set res [expr {$fraction1 > 0}]
+ .top.yt delete 1.0 end
+ .top.yt insert 1.0 $content
+ # synchronously wait for completion of line metrics calculation
+ # and ensure the test is relevant
+ set waited 0
+ if {[.top.yt pendingsync]} {set waited 1 ; vwait yud(.top.yt)}
+ lappend res $waited
+ .top.yt yview moveto $fraction1
+ set fraction2 [lindex [.top.yt yview] 0]
+ lappend res [expr {$fraction1 == $fraction2}]
+} -cleanup {
+ destroy .top.yt .top
+} -result {1 1 1}
+
+# edit, mark, scan, search, see, tag, window, xview and yview actions are tested elsewhere.
test text-12.1 {ConfigureText procedure} {
list [catch {.t2 configure -state foobar} msg] $msg
@@ -3698,7 +3824,7 @@ test text-31.2 {TextWidgetCmd procedure, "peer" option} {
list [catch {.t peer names foo} msg] $msg
} {1 {wrong # args: should be ".t peer names"}}
test text-31.3 {TextWidgetCmd procedure, "peer" option} {
- list [catch {.t p names} msg] $msg
+ list [catch {.t pee names} msg] $msg
} {0 {}}
test text-31.4 {TextWidgetCmd procedure, "peer" option} {
.t peer names
diff --git a/tests/textDisp.test b/tests/textDisp.test
index 5508d7c..90406df 100644
--- a/tests/textDisp.test
+++ b/tests/textDisp.test
@@ -4162,29 +4162,34 @@ test textDisp-33.5 {bold or italic fonts} win {
} {italic font measurement ok}
destroy .tt
-test textDisp-34.1 {Text widgets multi-scrolling problem: Bug 2677890} -setup {
- pack [text .t1 -width 10 -yscrollcommand {.sy set}] \
- [ttk::scrollbar .sy -orient vertical -command {.t1 yview}] \
- -side left -fill both
- bindtags .sy {}; # No clicky!
+test textDisp-34.1 {Line heights recalculation problem: bug 2677890} -setup {
+ pack [text .t1] -expand 1 -fill both
set txt ""
- for {set i 0} {$i < 99} {incr i} {
- lappend txt "$i" [list pc $i] "\n" ""
+ for {set i 1} {$i < 100} {incr i} {
+ append txt "Line $i\n"
}
set result {}
} -body {
- .t1 insert end {*}$txt
- update
- lappend result [.sy get]
- .t1 replace 6.0 6.0+1c "*"
- lappend result [.sy get]
- after 0 {lappend result [.sy get]}
- after 1000 {lappend result [.sy get]}
- vwait result;vwait result
- return $result
+ .t1 insert end $txt
+ .t1 debug 1
+ set ge [winfo geometry .]
+ scan $ge "%dx%d+%d+%d" width height left top
+ update
+ .t1 sync
+ set negative 0
+ bind .t1 <<WidgetViewSync>> { if {%d < 0} {set negative 1} }
+ # Without the fix for bug 2677890, changing the width of the toplevel
+ # will launch recomputation of the line heights, but will produce negative
+ # number of still remaining outdated lines, which is obviously wrong.
+ # Thus we use this way to check for regression regarding bug 2677890,
+ # i.e. to check that the fix for this bug really is still in.
+ wm geometry . "[expr {$width * 2}]x$height+$left+$top"
+ update
+ .t1 sync
+ set negative
} -cleanup {
- destroy .t1 .sy
-} -result {{0.0 0.24} {0.0 0.24} {0.0 0.24} {0.0 0.24}}
+ destroy .t1
+} -result {0}
test textDisp-35.1 {Init value of charHeight - Dancing scrollbar bug 1499165} -setup {
pack [text .t1] -fill both -expand y -side left