Monitors widget modifications, it causes a Tcl command to be executed whenever certain widget modifications are done. In general only user (GUI) modifications will be watched. But if option -always is specified then all modifications will be watched. In fact the triggering depends on the way how a certain modification will be executed:
If option -always is not specified then the delete command triggers only if the argument index1 (the starting point of the deletion) is specified with "insert" (the insertion cursor).
If option -always is not specified then the insert command triggers only if the argument index (the starting point of the insertion) is specified with "insert" (the insertion cursor).
If option -always is not specified then The mark set command triggers only if the argument markName (the name of the mark) is specified with "insert" (the insertion cursor).
If option -always is not specified then the replace command triggers only if the argument index1 (the starting point of the replacement) is specified with "insert" (the insertion cursor).
edit undo|redo will trigger the corresponding undo|redo event.
A resize or content change of a displayed image will trigger event
Any change of a displayed window (resize, map, or unmap) will trigger event window.
Additionally any of the following commands may trigger because the view may change:
Also destroying an embedded window, or embedded image, may trigger due to a changed view.
Note that the delete, and the insert operation will be handled in the same way as the replace operation, this means that firstly the deletion part triggers, and secondly the insertion part will be triggered. In case of delete the insertion part is empty (zero characters), and in case of insert the deletion range is empty.
CommandPrefix will be resolved using the usual namespace resolution rules used by commands. If the command does not exist, an error will be thrown (except the command argument is empty). When a certain widget modification triggers, a number of arguments are appended to commandPrefix so that the actual command is as follows:
UserFlag informs whether this is a user (GUI) modification. Note that this flag is meaningful only if op is delete, or insert.
Op indicates which operation has been performed:
Zero ore more characters have been inserted, this event will be triggered after the insertion has been realized. Index1 and index2 are specifying the range of the new characters after insertion, this range can be empty. Info will contain five items:
It should be avoided to call commands inside this event which will insert or delete as long as final is false, the result can be unexpected. Such commands should be postponed, either with the after command, or until final state is true.
This event will also be triggered for delete, and replace operations, in these cases zero characters may be inserted, and index1 refers to the start position of the deletion.
Zero or more characters will be deleted, this event will be triggered before the deletion has been realized. Index1 and index2 are specifying the range of the characters to be deleted, this range can be empty. Info will contain six items:
This event will also be triggered for insert, and replace operations, in these cases the deletion range may be empty, and index1 refers to the start position of the insertion.
It should be avoided to call commands inside this event which will insert or delete, the result can be unexpected. Such commands should be postponed, either with the after command, or until the corresponding insert event will be triggered.
The position of the insertion cursor has been moved. Index1 is the old character position, and can be empty, index2 is the new character position of the cursor. Here info contains a list of tags which would be applied if the user (GUI) is inserting a character at the new cursor position.
The view of the widget has changed (scrolling the widget, moving the cursor, the see command has been executed, or any other modifying command has been performed – see the list of modifiying commands above). Here index1 specifies the pixel position of the upper left corner in the form @x,y, and index2 specifies the pixel position of the lower right corner. Info does not have a meaning here and will be empty.
An undo action has been performed. Info contains two elements. The first is the performed undo command, one of: delete, image, insert, mark, tag, or window. The second is a boolean flag indicating whether this is the final part of a multi-undo action.
It is not allowed to perform any textual modification, nor is it allowed to reset the undo/redo stack, an error will be thrown.
A redo action has been performed. Info contains two elements. The first is the performed redo command, one of: delete, image, insert, mark, tag, or window. The second is a boolean flag indicating whether this is the final part of a multi-redo action.
It is not allowed to perform any textual modification, nor is it allowed to reset the undo/redo stack, an error will be thrown.
An image has been resized, or the content has changed. Info contains the name of the affected image as first element. In case of a change in size the width and height of the image before resizing will be the second and third element of info.
Either a window has changed the display state (mapped or unmapped), or the size of the window has changed. Info contains the name of the affected window as first element. And if the size of the window has changed, then info will provide the old width and height with a second and third element.
PathName is the name of the widget where the modification has been done. In case of insert/delete also the modifications in a different peer will be triggered for this widget, provided that the range of the modification is inside the displayed range. It is guaranteed that the widget, where the insert/delete has been done, will be triggered at first, but the order of triggering other peers is unspecified.
If commandPrefix is not specified, then the watch of modifications will be terminated. Otherwise it will replace any existing script, but if the first character is “+” then the new script augments an existing script.
This command returns the command prefix which has been set before this call, this can be empty if a command prefix was not set.
This is a big help for the implementation of editors, especially the support of syntax highlighting is important, and the watch command makes this support relatively easy. Furthermore it supports the implementation of many other features like line numbering (see stackoverflow). Watching the insertion cursor allows a clear distinction between user (GUI) actions and programmed actions, a proper developed editor is in general not using the "insert" mark for edit operations.
The events are designed in a way so that the replace operation does not need a special handling, and in case of a delete operation the user (GUI) will receive two states: the state before deletion, and the state after deletion (with the additional triggering of insert). The same applies to insert, the state before insertion is done will be received (with triggering delete), and the state after insertion will be received.
The implementation of the watch command does not require any change or extension of other functions, it is completely independent. Of course the triggering has been added at a few places.
A few changes in the Tcl implementation (library/text.tcl) were required, because any edit operation in this file has to trigger the watch command.
Below is an example for the usage of the watch command. This widget shows the line numbers, and is highlighting the cursor line.
proc showLineNumbers {w topLeft} {
global yh
set startLineNo [lindex [split [$w index @first,first] .] 0]
set endLineNo [lindex [split [$w index @first,last] .] 0]
set ymin [lindex [split $topLeft ,] 1]
set y0 [$w count -ypixels begin $startLineNo.begin]
for {set lineNo $startLineNo; set n 1} {$lineNo <= $endLineNo} {incr lineNo; incr n} {
if {[llength [.c gettags $n]] == 0} {
.c create text 0 0 -anchor ne -width 0 -justify left -tags $n
if {$yh == 0} { set yh [font metrics [.c itemcget $n -font] -linespace] }
}
set y1 [$w count -ypixels begin [expr {$lineNo + 1}].begin]
.c itemconfigure $n -text $lineNo -state normal
.c coords $n 30 [expr {$y0 + 1 + ($y1 - $y0 - $yh)/2 - $ymin}]
set y0 $y1
}
# Hide unused line numbers.
while {[llength [.c gettags $n]] && [.c itemcget $n -state] == "normal"} {
.c itemconfigure $n -state hidden
incr n
}
}
proc HighlightCurrentLine {w} {
set next begin
while {[llength [set next [$w tag next line $next]]] > 0} {
$w tag remove line {*}$next
set next [lindex $next 1]
}
if {"line" ni [$w tag names insert] ||
"line" ni [$w tag names "insert linestart"] ||
"line" ni [$w tag names "insert lineend"]} {
$w tag add line "insert linestart" "insert lineend +1c"
}
}
proc highlightCurrentLine {w} {
global afterHCL
after cancel $afterHCL
set afterHCL [after idle [list HighlightCurrentLine $w]]
}
proc printColumn {w} {
$w edit info
append text "Column: [expr {[lindex [split [$w index insert] .] 1] + 1}]"
.l configure -text $text
}
proc watch {w op idx1 idx2 info userFlag} {
switch $op {
delete { if {$idx1 ne $idx2} { highlightCurrentLine $w } }
view { showLineNumbers $w $idx1 }
cursor { highlightCurrentLine $w; printColumn $w }
undo - redo { if {[lindex $info 1]} { highlightCurrentLine $w } }
}
}
set afterHCL {}
set normal [list [font configure TkTextFont -family] -14]
set bold [list [font configure TkTextFont -family] -18 bold]
set yh 0
set c [canvas .c -width 35]
set l [ttk::label .l]
set w [text .t \
-width 70 \
-height 30 \
-state normal \
-font $normal \
-background #000040 \
-foreground #c0c0c0 \
-insertbackground #ffff00 \
-insertforeground black \
-showinsertforeground yes \
-selectforeground black \
-blockcursor on \
-insertofftime 0 \
]
$w watch watch
focus $w
grid $c -row 0 -column 0 -sticky sn
grid $w -row 0 -column 1 -sticky snew
grid $l -row 1 -column 1 -sticky ew
grid columnconfigure . {1} -weight 1
grid rowconfigure . {0} -weight 1
$w tag configure line -background #030461 -undo no
$w tag configure header -font $bold
$w tag raise sel
$w insert end "Accelerating Searches in Display Code" header
$w insert end "\n
In the time critical display stuff at serveral places the
search for start of logical line, search for next logical
line, and search for start/end of elided range is required.
All these searches are quite slow, the worst case complexity
is in general O(n), if n is the number of lines. In revised
version all the searches will be done with the B-Tree, now
the worst case complexity is O(log n).
\n"
$w insert end "Additional Caching of Display Line Heights" header
$w insert end "\n
The current behavior with caching only the line metric of
physical lines has one drawback: scrolling, especially
scrolling upward, can be very slow (this means the behavior
is jumpy, and sometimes it feels as if the widget is
hanging), if very long lines, producing many display lines,
are involved. In revised version the line metric of all
display lines will be cached, in this way the repeated
computation of the display lines, which are currently not
visible, but required for line scroll, is eliminated. The
mouse hovering was infected in the same way (repeated
computation of display lines), this problem is also gone.
\n"
$w insert end "Enhanced Update of Line Metrics" header
$w insert end "\n
The widget is computing asynchronously the line metrics,
this is required for the scrollbar, and for the acceleration
of some operations. The old algorithm is quite slow in some
egde cases. Instead of iterating over all lines now a range
list will be used, in this way these edge cases are not
critical anymore.
\n"
$w insert end "Enhanced Algorithms" header
$w insert end "\n
Some of the display functions, especially
TkTextIndexYPixels(), TkTextMeasureDown(), YScrollByLines(),
GetYPixelCount(), and MeasureUp(), are iterating over lines
while computing the line metric (again). The revised version
don't need iteration, it works with B-Tree lookups, and the
line metric will be computed only once. In revised version
now any call of LayoutDLine() will update the line metric
cache, so no more reduntant line metric computation like in
the old code.
\n"
$w insert end "Reduced Display Line Computation" header
$w insert end "\n
The old algorithms are a bit stupid. One example: some text
will be inserted into the widget, and afterwards the text
view will be positioned to the end of the text. Because the
line metric computation is not yet done – we assume that no
update has been triggered in the meanwhile – all display
lines will be computed until the last display line metric is
known, and then all visible display lines will be computed
again for displaying. The revised implementation is using
the result of the line metric computation for displaying –
no repeated computation –, and only after displaying has
been done the superfluous display lines will be deleted. The
decrease of display line computation is expecially important
for the Mac, because here the line metric computation is
quite expensive due to required sub-pixel accuracy.\n
Another example: when scrolling the widget content it
happens with old algorithm that some display lines at start
will be deleted, and immediately afterwards some of the
deleted display lines will be re-computed for filling empty
space at top of widget. The revised algorithm avoids the
re-computation.
\n"
$w insert end "Scrolling is not Flickering Anymore" header
$w insert end "\n
Scrolling the widget is flickering (wish8.6). When scrolling
with mouse wheel tagged text content may flicker, because
the tagged region may receive a mouse-over event for a short
time, and this might change temporarily some tag options.
This is especially very nasty when the line metric is
changing while the mouse is hovering, the effect is a
volatile screen.\n
In revised version (per default; see option -responsiveness)
the repick (sending event/leave/motion events) will be done
after the successive scroll operations have been finished,
not between the successive scroll operations as in old
widget.
\n"
$w insert end "Mouse Hovering" header
$w insert end "\n
The mouse hovering requires an x,y position lookup inside
the display lines. In revised version the last hovered
display chunk will be cached, in this way a new search
over the display lines will only happen if the mouse cursor
is leaving the cached chunk. This is reducing the number of
computations significantly. Moreover the display line now
contains chunk sections, this provides a fast mapping
between x-coordinates and byte indices, the increase in
speed is at least factor 10."
$w mark set insert begin
$w configure -undo on
# vi:set ts=8 sw=4 et: