From 5aca2c785542acbefba1c55007389e41d19ea6f3 Mon Sep 17 00:00:00 2001 From: vincentdarley Date: Fri, 31 Oct 2003 09:02:06 +0000 Subject: TIP 155 implementation --- ChangeLog | 30 + doc/text.n | 398 +++++-- generic/tkCanvas.c | 6 +- generic/tkText.c | 1609 ++++++++++++++++++++++----- generic/tkText.h | 135 ++- generic/tkTextBTree.c | 325 +++++- generic/tkTextDisp.c | 2687 +++++++++++++++++++++++++++++++++++++--------- generic/tkTextImage.c | 239 ++--- generic/tkTextIndex.c | 679 ++++++++++-- generic/tkTextMark.c | 37 +- generic/tkTextTag.c | 124 ++- generic/tkTextWind.c | 38 +- library/text.tcl | 110 +- mac/tkMacDefault.h | 3 +- macosx/tkMacOSXDefault.h | 3 +- tests/text.test | 765 ++++++++++++- tests/textDisp.test | 486 +++++++-- tests/textImage.test | 10 +- tests/textIndex.test | 92 +- tests/textWind.test | 8 +- unix/tkUnixDefault.h | 3 +- win/tkWinDefault.h | 3 +- 22 files changed, 6402 insertions(+), 1388 deletions(-) diff --git a/ChangeLog b/ChangeLog index 13fe3ab..64c143c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,35 @@ 2003-10-31 Vince Darley + * generic/tkText.c + * generic/tkTextIndex.c + * generic/tkTextDisp.c + * generic/tkTextWind.c + * generic/tkTextImage.c + * generic/tkTextTag.c + * generic/tkTextMark.c + * generic/tkTextBTree.c + * generic/tkText.h + * doc/text.n + * tests/text.test + * tests/textDisp.test + * tests/textImage.test + * tests/textIndex.test + * tests/textWind.test + * library/text.tcl + * generic/tkCanvas.c + * unix/tkUnixDefault.h + * win/tkWinDefault.h + * mac/tkMacDefault.h + * macosx/tkMacOSXDefault.h -- TIP 155 implementation + + This adds the 'count' and 'replace' subcommands to the text + widget, the '-blockcursor' option, and in particular provides + correct, smooth pixel-based scrolling of the widget under all + circumstances. See the text.n man page for the complete new + documentation. + +2003-10-31 Vince Darley + * win/tkWinMenu.c: more correct placing of images in compound menu entries. diff --git a/doc/text.n b/doc/text.n index 16f186d..dbd29d9 100644 --- a/doc/text.n +++ b/doc/text.n @@ -5,7 +5,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: text.n,v 1.18 2003/10/06 22:14:26 jenglish Exp $ +'\" RCS: @(#) $Id: text.n,v 1.19 2003/10/31 09:02:07 vincentdarley Exp $ '\" .so man.macros .TH text n 8.4 Tk "Tk Built-In Commands" @@ -38,6 +38,12 @@ Specifies a boolean that says whether separators are automatically inserted in the undo stack. Only meaningful when the \fB\-undo\fR option is true. .VE 8.4 +.OP \-blockcursor blockCursor BlockCursor +.VS 8.5 +Specifies a boolean that says whether the blinking insertion cursor +should be drawn as a character-sized rectangular block. If false +(the default) a thin vertical line is used for the insertion cursor. +.VE 8.5 .OP \-height height Height Specifies the desired height for the window, in units of characters in the font given by the \fB\-font\fR option. @@ -176,10 +182,20 @@ Indices have the syntax .CS \fIbase modifier modifier modifier ...\fR .CE -Where \fIbase\fR gives a starting point and the \fImodifier\fRs -adjust the index from the starting point (e.g. move forward or -backward one character). Every index must contain a \fIbase\fR, -but the \fImodifier\fRs are optional. +Where \fIbase\fR gives a starting point and the \fImodifier\fRs adjust +the index from the starting point (e.g. move forward or backward one +character). Every index must contain a \fIbase\fR, but the +\fImodifier\fRs are optional. Most modifiers (as documented below) allow +an optional submodifier. Valid \fIsubmodifier\fRs are nothing, \fIany\fB +or \fIdisplay\fB. If the submodifier is abbreviated, then it must be +followed by whitespace, but otherwise there need be no space between the +submodifier and the following \fImodifier\fR. Typically the \fIdisplay\fB +submodifier adjusts the meaning of the following \fImodifier\fR to make +it refer to visual or non-elided units rather than logical units, but +this is explained for each relevant case below. Lastly, where \fIcount\fB +is used as part of a modifier, it can be positive or negative, so '\fIbase\fR +- -3 lines' is perfectly valid (and equivalent to '\fIbase\fR ++3lines'). .PP The \fIbase\fR for an index must have one of the following forms: .TP 12 @@ -233,63 +249,116 @@ If modifiers follow the base index, each one of them must have one of the forms listed below. Keywords such as \fBchars\fR and \fBwordend\fR may be abbreviated as long as the abbreviation is unambiguous. .TP -\fB+ \fIcount\fB chars\fR -Adjust the index forward by \fIcount\fR characters, moving to later -lines in the text if necessary. If there are fewer than \fIcount\fR -characters in the text after the current index, then set the index -to the last character in the text. -Spaces on either side of \fIcount\fR are optional. -.TP -\fB\- \fIcount\fB chars\fR +\fB+ \fIcount\fB \fI?submodifier?\fB chars\fR +Adjust the index forward by \fIcount\fR characters, moving to later lines +in the text if necessary. If there are fewer than \fIcount\fR characters +in the text after the current index, then set the index to the last index +in the text. Spaces on either side of \fIcount\fR are optional. If the +\fIdisplay\fB submodifier is given, elided characters are skipped over +without being counted. If \fIany\fB is given, then all characters are +counted. For historical reasons, if neither modifier is given then the +count actually takes place in units of index positions (see \fBindices\fR +for details). This behaviour may be changed in a future major release, +so if you need an index count, you are encouraged to use \fBindices\fR +instead wherever possible. +.TP +\fB\- \fIcount\fB \fI?submodifier?\fB chars\fR Adjust the index backward by \fIcount\fR characters, moving to earlier lines in the text if necessary. If there are fewer than \fIcount\fR -characters in the text before the current index, then set the index -to the first character in the text. -Spaces on either side of \fIcount\fR are optional. -.TP -\fB+ \fIcount\fB lines\fR +characters in the text before the current index, then set the index to +the first index in the text (1.0). Spaces on either side of \fIcount\fR +are optional. If the \fIdisplay\fB submodifier is given, elided +characters are skipped over without being counted. If \fIany\fB is +given, then all characters are counted. For historical reasons, if +neither modifier is given then the count actually takes place in units of +index positions (see \fBindices\fR for details). This behaviour may be +changed in a future major release, so if you need an index count, you are +encouraged to use \fBindices\fR instead wherever possible. +.TP +\fB+ \fIcount\fB \fI?submodifier?\fB indices\fR +Adjust the index forward by \fIcount\fR index positions, moving to later +lines in the text if necessary. If there are fewer than \fIcount\fR +index positions in the text after the current index, then set the index +to the last index position in the text. Spaces on either side of +\fIcount\fR are optional. Note that an index position is either a single +character or a single embedded image or embedded window. If the +\fIdisplay\fB submodifier is given, elided indices are skipped over +without being counted. If \fIany\fB is given, then all indices are +counted, which is also the default behaviour if no modifier is given. +.TP +\fB\- \fIcount\fB \fI?submodifier?\fB indices\fR +Adjust the index backward by \fIcount\fR index positions, moving to +earlier lines in the text if necessary. If there are fewer than +\fIcount\fR index positions in the text before the current index, then +set the index to the first index position (1.0) in the text. Spaces on +either side of \fIcount\fR are optional. If the \fIdisplay\fB +submodifier is given, elided indices are skipped over without being +counted. If \fIany\fB is given, then all indices are counted, which is +also the default behaviour if no modifier is given. +.TP +\fB+ \fIcount\fB \fI?modifier?\fB lines\fR Adjust the index forward by \fIcount\fR lines, retaining the same character position within the line. If there are fewer than \fIcount\fR -lines after the line containing the current index, then set the index -to refer to the same character position on the last line of the text. -Then, if the line is not long enough to contain a character at the indicated -character position, adjust the character position to refer to the last -character of the line (the newline). -Spaces on either side of \fIcount\fR are optional. -.TP -\fB\- \fIcount\fB lines\fR -Adjust the index backward by \fIcount\fR lines, retaining the same -character position within the line. If there are fewer than \fIcount\fR -lines before the line containing the current index, then set the index -to refer to the same character position on the first line of the text. -Then, if the line is not long enough to contain a character at the indicated +lines after the line containing the current index, then set the index to +refer to the same character position on the last line of the text. Then, +if the line is not long enough to contain a character at the indicated character position, adjust the character position to refer to the last -character of the line (the newline). -Spaces on either side of \fIcount\fR are optional. -.TP -\fBlinestart\fR -Adjust the index to refer to the first character on the line. -.TP -\fBlineend\fR -Adjust the index to refer to the last character on the line (the newline). -.TP -\fBwordstart\fR +character of the line (the newline). Spaces on either side of +\fIcount\fR are optional. If the \fIdisplay\fB submodifier is given, +then each visual display line is counted separately. Otherwise, if +\fIany\fB (or no modifier) is given, then each logical line (no matter +how many times it is visually wrapped) counts just once. If the relevant +lines are not wrapped, then these two methods of counting are equivalent. +.TP +\fB\- \fIcount\fB \fI?submodifier?\fB lines\fR +Adjust the index backward by \fIcount\fR logical lines, retaining the +same character position within the line. If there are fewer than +\fIcount\fR lines before the line containing the current index, then set +the index to refer to the same character position on the first line of +the text. Then, if the line is not long enough to contain a character at +the indicated character position, adjust the character position to refer +to the last character of the line (the newline). Spaces on either side +of \fIcount\fR are optional. If the \fIdisplay\fB submodifier is given, +then each visual display line is counted separately. Otherwise, if +\fIany\fB (or no modifier) is given, then each logical line (no matter +how many times it is visually wrapped) counts just once. If the relevant +lines are not wrapped, then these two methods of counting are equivalent. +.TP +\fB?submodifier? linestart\fR +Adjust the index to refer to the first character on the line. If the +\fIdisplay\fB submodifier is given, this is the first character on the +display line, otherwise on the logical line. +.TP +\fB?submodifier? lineend\fR +Adjust the index to refer to the last character on the line (the +newline). If the \fIdisplay\fB submodifier is given, this is the last +character on the display line, otherwise on the logical line. +.TP +\fB?submodifier? wordstart\fR Adjust the index to refer to the first character of the word containing the current index. A word consists of any number of adjacent characters -that are letters, digits, or underscores, or a single character that -is not one of these. +that are letters, digits, or underscores, or a single character that is +not one of these. If the \fIdisplay\fB submodifier is given, this only +examines non-elided characters, otherwise all characters (elided or not) +are examined. .TP -\fBwordend\fR +\fB?submodifier? wordend\fR Adjust the index to refer to the character just after the last one of the -word containing the current index. If the current index refers to the last -character of the text then it is not modified. +word containing the current index. If the current index refers to the +last character of the text then it is not modified. If the \fIdisplay\fB +submodifier is given, this only examines non-elided characters, otherwise +all characters (elided or not) are examined. .PP If more than one modifier is present then they are applied in left-to-right order. For example, the index ``\fBend \- 1 chars\fR'' refers to the next-to-last character in the text and ``\fBinsert wordstart \- 1 c\fR'' refers to the character just before the first one in the word containing the insertion cursor. - +.PP +Where modifiers result in index changes by display lines, display chars +or display indices and the \fIbase\fP refers to an index which is elided, +that base index is considered to be equivalent to the first following +non-elided index. .SH TAGS .PP The first form of annotation in text widgets is a tag. @@ -560,7 +629,7 @@ parent). The embedded window's position on the screen will be updated as the text is modified or scrolled, and it will be mapped and unmapped as it moves into and out of the visible area of the text widget. -Each embedded window occupies one character's worth of index space +Each embedded window occupies one unit's worth of index space in the text widget, and it may be referred to either by the name of its embedded window or by its position in the widget's index space. @@ -627,7 +696,7 @@ and a particular image may be embedded in multiple places in the same text widget. The embedded image's position on the screen will be updated as the text is modified or scrolled. -Each embedded image occupies one character's worth of index space +Each embedded image occupies one unit's worth of index space in the text widget, and it may be referred to either by its position in the widget's index space, or the name it is assigned when the image is inserted into the text widget widh \fBimage create\fP. @@ -739,14 +808,13 @@ The text widget has an unlimited undo and redo mechanism (when the \fB-undo\fR widget option is true) which records every insert and delete action on a stack. .PP -Boundaries (called "separators") are inserted between edit actions. -The purpose of these separators is to group inserts and deletes into -one compound edit action. When undoing a change everything between -two separators will be undone. The undone changes are then moved to -the redo stack, so that an undone edit can be redone again. The redo -stack is cleared whenever new edit actions are recorded on the undo -stack. The undo and redo stacks can be cleared to keep their depth -under control. +Boundaries (called "separators") are inserted between edit actions. The +purpose of these separators is to group inserts, deletes and replaces +into one compound edit action. When undoing a change everything between +two separators will be undone. The undone changes are then moved to the +redo stack, so that an undone edit can be redone again. The redo stack +is cleared whenever new edit actions are recorded on the undo stack. The +undo and redo stacks can be cleared to keep their depth under control. .PP Separators are inserted automatically when the \fB-autoseparators\fR widget option is true. You can insert separators programatically as @@ -821,6 +889,66 @@ this case the command returns an empty string. \fIOption\fR may have any of the values accepted by the \fBtext\fR command. .TP +\fIpathName \fBcount \fI?options\fI? \fIindex1 \fIindex2\fR +Counts the number of relevant things between the two indices. If index1 +is after index2, the result will be a negative number (and this holds for +each of the possible options). The actual items which are counted depend +on the options given. The result is a list of integers, one for the +result of each counting option given. Valid counting options are +'-chars', '-displaychars', '-displayindices', '-displaylines', +'-indices', '-lines', '-xpixels' and '-ypixels'. The default value, if +no option is specified, is '-indices'. There is an additional possible +option '-update' which is a modifier. If given, then all subsequent +options ensure that any possible out of date information is recalculated. +This currently only has any effect for the '-ypixels' count (which, if +'update' is not given, will use the text widget's current cached value +for each line). The count options are interpreted as follows: +.TP +\fI-chars\fR - count all characters, whether elided or not. Do not count +embedded windows or images. +.TP +\fI-displaychars\fR - count all non-elided characters. +.TP +\fI-displayindices\fR - count all non-elided characters, windows and +images. +.TP +\fI-displaylines\fR - count all display lines (i.e. counting one for each +time a line wraps) from the line of the first index up to, but not +including the display line of the second index. Therefore if they are +both on the same display line, zero will be returned. By definition +displaylines are visible and therefore this only counts portions of +actual visible lines. +.TP +\fI-indices\fR - count all characters and embedded windows or images (i.e. +everything which counts in text-widget index space), whether they are +elided or not. +.TP +\fI-lines\fR - count all logical lines (irrespective of wrapping) from +the line of the first index up to, but not including the line of the +second index. Therefore if they are both on the same line, zero will be +returned. Logical lines are counted whether they are currently visible +(non-elided) or not. +.TP +\fI-xpixels\fR - count the number of horizontal pixels from the first +pixel of the first index to (but not including) the first pixel of the +second index. To count the total desired width of the text widget +(assuming wrapping is not enabled), first find the longest line and then +use '.text count -xpixels "${line}.0" "${line}.0 lineend"'. +.TP +\fI-ypixels\fR - count the number of vertical pixels from the first pixel +of the first index to (but not including) the first pixel of the second +index. If both indices are on the same display line, zero will be +returned. To count the total number of vertical pixels in the text +widget, use '.text count -ypixels 1.0 end', and to ensure this is up to +date, use '.text count -update -ypixels 1.0 end'. +.TP +The command returns a positive or negative integer corresponding to the +number of items counted between the two indices. One such integer is +returned for each counting option given, so a list is returned if more +than one option was supplied. For example '.text count -xpixels +-ypixels 1.3 4.5' is perfectly valid and will return a list of two +elements. +.TP \fIpathName \fBdebug \fR?\fIboolean\fR? If \fIboolean\fR is specified, then it must have one of the true or false values accepted by Tcl_GetBoolean. @@ -979,7 +1107,7 @@ option is false. .RE .VE 8.4 .TP -\fIpathName \fBget \fIindex1 \fR?\fIindex2 ...\fR? +\fIpathName \fBget \fI?-displaychars?\fR \fI--fR \fIindex1 \fR?\fIindex2 ...\fR? Return a range of characters from the text. The return value will be all the characters in the text starting with the one whose index is \fIindex1\fR and ending just before @@ -997,6 +1125,11 @@ If multiple index pairs are given, multiple ranges of text will be returned in a list. Invalid ranges will not be represented with empty strings in the list. The ranges are returned in the order passed to \fBget\fR. .VE 8.4 +.VS 8.5 +If the \fI-displaychars\fR option is given, then, within each range, +only those characters which are not elided will be returned. This may +have the effect that some of the returned ranges are empty strings. +.VE 8.5 .TP \fIpathName \fBimage \fIoption \fR?\fIarg arg ...\fR? This command is used to manipulate embedded images. @@ -1127,6 +1260,20 @@ returned by future calls to ``\fIpathName \fBmark names\fR''. This command returns an empty string. .RE .TP +\fIpathName \fBreplace \fIindex1 \fIindex2 chars \fR?\fItagList chars tagList ...\fR? +Replaces the range of characters between \fIindex1\fR and \fIindex2\fR +with the given characters and tags. See the section on \fIpathName +\fBinsert\fR for an explanation of the handling of the \fItagList...\fR +arguments, and the section on \fIpathName +\fBdelete\fR for an explanation of the handling of the indices. If +\fIindex2\fR corresponds to an index earlier in the text than +\fIindex1\fR, an error will be generated. +.TP +The deletion and insertion are arranged so that no unnecessary +scrolling of the window or movement of insertion cursor occurs. In +addition the undo/redo stack are correctly modified, if undo operations +are active in the text widget. The command returns an empty string. +.TP \fIpathName \fBscan\fR \fIoption args\fR This command is used to implement scanning on texts. It has two forms, depending on \fIoption\fR: @@ -1164,11 +1311,12 @@ matching range starting at or after the position given by \fIindex\fR. This is the default. .TP \fB\-backwards\fR -The search will proceed backward through the text, finding the -matching range closest to \fIindex\fR whose first character -is before \fIindex\fR. Note that, for a variety of reasons, backwards -searches can be substantially slower than forwards searches, so it is -recommended that performance-critical code use forward searches. +The search will proceed backward through the text, finding the matching +range closest to \fIindex\fR whose first character is before \fIindex\fR +(it is not allowed to be at \fIindex\fR). Note that, for a variety of +reasons, backwards searches can be substantially slower than forwards +searches (particularly when using \fB\-regexp\fR), so it is recommended +that performance-critical code use forward searches. .TP \fB\-exact\fR Use exact matching: the characters in the matching range must be @@ -1185,11 +1333,11 @@ to the regexp engine (unless \fB\-nolinestop\fR is used), so that sequences will never match the newline character \fI\en\fR. .TP \fB\-nolinestop\fR -This allows -\fI.\fR and \fI[^\fR sequences to match the newline character \fI\en\fR, -which they will otherwise not do (see the \fBregexp\fR +This allows \fI.\fR and \fI[^\fR sequences to match the newline +character \fI\en\fR, which they will otherwise not do (see the \fBregexp\fR command for details). This option is only meaningful if \fB\-regexp\fR -is also given, and an error will be thrown otherwise. +is also given, and an error will be thrown otherwise. For example, +to match the entire text, use 'search -nolinestop -regexp ".*" 1.0'. .TP \fB\-nocase\fR Ignore case differences between the pattern and the text. @@ -1198,7 +1346,8 @@ Ignore case differences between the pattern and the text. The argument following \fB\-count\fR gives the name of a variable; if a match is found, the number of index positions between beginning and end of the matching range will be stored in the variable. If there are no -embedded images or windows in the matching range, this is equivalent to the +embedded images or windows in the matching range (and there are no +elided characters if \fB\-elide\fR is not given), this is equivalent to the number of characters matched. In either case, the range \fImatchIdx\fR to \fImatchIdx + $count chars\fR will return the entire matched text. .TP @@ -1208,7 +1357,22 @@ the first character of each match. If a \fB\-count\fI varName\fR switch is given, then \fBvarName\fR is also set to a list containing one element for each successful match. Note that, even for exact searches, the elements of this list may be different, if there are embedded images, -windows or hidden text. +windows or hidden text. Searches with \fB\-all\fR behave very +similarly to the Tcl command \fB\regexp -all\fR, in that overlapping +matches are not normally returned. For example, applying an \fB\-all\fR search +of the pattern '\\w+' against 'hello there' will just match twice, once +for each word, and matching 'Z[a-z]+Z' against 'ZooZooZoo' will just +match once. +.TP +\fB\-overlap\fR +When performing \fB\-all\fR searches, the normal behaviour is that +matches which overlap an already-found match will not be returned. This +switch changes that behaviour so that all matches which are not totally +enclosed within another match are returned. For example, applying an +\fB\-overlap\fR search of the pattern '\\w+' against 'hello there' will +just match twice (i.e. no different to just \fB\-all\fR), but matching +'Z[a-z]+Z' against 'ZooZooZoo' will now match twice. +An error will be thrown if this switch is used without \fB\-all\fR. .TP \fB\-elide\fR Find elidden (hidden) text as well. By default only displayed text is @@ -1219,11 +1383,11 @@ This switch has no effect except to terminate the list of switches: the next argument will be treated as \fIpattern\fR even if it starts with \fB\-\fR. .LP -The matching range must be entirely within a single line of text. -For regular expression matching the newlines are removed from the ends -of the lines before matching: use the \fB$\fR feature in regular -expressions to match the end of a line. -For exact matching the newlines are retained. +The matching range may be within a single line of text, or run across +multiple lines (if parts of the pattern can match a new-line). For +regular expression matching one can use the various newline-matching +features such as \fB$\fR to match the end of a line, and to control +whether \fB.\fR is allowed to match a new-line. If \fIstopIndex\fR is specified, the search stops at that index: for forward searches, no match at or after \fIstopIndex\fR will be considered; for backward searches, no match earlier in the @@ -1231,7 +1395,11 @@ text than \fIstopIndex\fR will be considered. If \fIstopIndex\fR is omitted, the entire text will be searched: when the beginning or end of the text is reached, the search continues at the other end until the starting location is reached -again; if \fIstopIndex\fR is specified, no wrap-around will occur. +again; if \fIstopIndex\fR is specified, no wrap-around will occur. +This means that, for example, if the search is \fB\-forwards\fR +but \fIstopIndex\fR is earlier in the text than \fIstartIndex\fR, +nothing will ever be found. See "KNOWN BUGS" below for a number of +minor limitations of the search command. .RE .TP \fIpathName \fBsee \fIindex\fR @@ -1545,14 +1713,17 @@ way through the text, and so on. \fIpathName \fByview scroll \fInumber what\fR This command adjust the view in the window up or down according to \fInumber\fR and \fIwhat\fR. -\fINumber\fR must be an integer. -\fIWhat\fR must be either \fBunits\fR or \fBpages\fR. -If \fIwhat\fR is \fBunits\fR, the view adjusts up or down by -\fInumber\fR lines on the display; if it is \fBpages\fR then -the view adjusts by \fInumber\fR screenfuls. -If \fInumber\fR is negative then earlier positions in the text -become visible; if it is positive then later positions in the text -become visible. +\fIWhat\fR must be \fBunits\fR, \fBpages\fR or \fBpixels\fR. +If \fIwhat\fR is \fBunits\fR or \fBpages\fR then \fInumber\fR must be an +integer, otherwise number may be specified in any of the forms acceptable +to \fBTk_GetPixels\fR, such as ``2.0c'' or ``1i'' (the result is rounded +to the nearest integer value. If no units are given, pixels are +assumed). If \fIwhat\fR is \fBunits\fR, the view adjusts up or down by +\fInumber\fR lines on the display; if it is \fBpages\fR then the view +adjusts by \fInumber\fR screenfuls; if it is \fBpixels\fR then the view +adjusts by \fInumber\fR pixels. If \fInumber\fR is negative then earlier +positions in the text become visible; if it is positive then later +positions in the text become visible. .TP \fIpathName \fByview \fR?\fB\-pickplace\fR? \fIindex\fR Changes the view in the widget's window to make \fIindex\fR visible. @@ -1759,6 +1930,18 @@ take place. The behavior of texts can be changed by defining new bindings for individual widgets or by redefining the class bindings. +.SH "ISSUES CONCERNING CHARS AND INDICES" +.PP +Before Tk 8.5, the widget used the string "chars" to refer to index +positions (which included characters, embedded windows and embedded +images). As of Tk 8.5 the text widget deals separately and correctly +with "chars" and "indices". For backwards compatibility, however, the +index modifiers "+N chars" and "-N chars" continue to refer to indices. +One must use any of the full forms "+N any chars" or "-N any chars" etc +to refer to actual character indices. This confusion may be fixed in a +future release by making the widget correctly interpret "+N chars" as a +synonym for "+N any chars". + .SH "PERFORMANCE ISSUES" .PP Text widgets should run efficiently under a variety @@ -1792,5 +1975,54 @@ especially if they have many marks and tags within them. The display line with the insert cursor is redrawn each time the cursor blinks, which causes a steady stream of graphics traffic. Set the \fBinsertOffTime\fP attribute to 0 avoid this. + +.SH "KNOWN BUGS" +.PP +The \fB-elide\fP tag attribute, introduced in Tk 8.3, has one known +limitation. Newlines which have this attribute are only actually elided +if their entire logical line is also elided. Where there is a logical +line which begins un-elided but terminates with an elided newline, that +newline will still actually cause a line-break in the display. The basic +limitation is that while a single logical line can result in multiple +display lines, a single display line cannot be derived from multiple +logical lines. This does mean, however, that logical lines which are +completely elided have no problems. + +The \fBsearch -regexp\fR sub-command attempts to perform sophisticated +regexp matching across multiple lines in an efficient fashion (since Tk +8.5), examing each line individually, and then in small groups of lines, +whether searching forwards or backwards. Under certain conditions the +search result might differ from that obtained by applying the same regexp +to the entire text from the widget in one go. For example, when +searching with a greedy regexp, the widget will continue to attempt to +add extra lines to the match as long as one of two conditions are true: +either Tcl's regexp library returns a code to indicate a longer match is +possible (but there are known bugs in Tcl which mean this code is not +always correctly returned); or if each extra line added results in at +least a partial match with the pattern. This means in the case where the +first extra line added results in no match and Tcl's regexp system +returns the incorrect code and adding a second extra line would actually +match, the text widget will return the wrong result. In practice this is +a rare problem, but it can occur, for example: \fBpack [text .t] ; .t +insert 1.0 "aaaa\nbbbb\ncccc\nbbbb\naaaa\n" ; .t search -regexp -- +{(a+|b+\nc+\nb+)+\na+} 1.0\fR will not find a match when one exists of 19 +characters starting from the first 'b'. + +Whenever one possible match is fully enclosed in another, the search +command will attempt to ensure only the larger match is returned. +When performing backwards regexp searches it is possible that Tcl +will not always achieve this, in the case where a match is preceded by +one or more short, non-overlapping matches, all of which are preceded +by a large match which actually encompasses all of them. The search +algorithm used by the widget does not look back arbitrarily far for a +possible match which might cover large portions of the widget. For +example: \fBpack [text .t] ; .t insert 1.0 "aaaa\nbbbb\nbbbb\nbbbb\nbbbb\n" +; .t search -regexp -backward -- {b+\n|a+\n(b+\n)+} end\fR matches at +'5.0' when a true greedy match would match at '1.0'. Similarly if we +add \fB-all\fR to this case, it matches at all of '5.0', '4.0', '3.0' +and '1.0', when really it should only match at '1.0' since that match +encloses all the others. + + .SH KEYWORDS text, widget, tkvars diff --git a/generic/tkCanvas.c b/generic/tkCanvas.c index 4bc01dd..e1068b0 100644 --- a/generic/tkCanvas.c +++ b/generic/tkCanvas.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkCanvas.c,v 1.23 2003/09/29 23:15:19 dkf Exp $ + * RCS: @(#) $Id: tkCanvas.c,v 1.24 2003/10/31 09:02:08 vincentdarley Exp $ */ /* #define USE_OLD_TAG_SEARCH 1 */ @@ -291,7 +291,7 @@ static int FindArea _ANSI_ARGS_((Tcl_Interp *interp, TkCanvas *canvasPtr, Tcl_Obj *CONST *argv, Tk_Uid uid, int enclosed)); static double GridAlign _ANSI_ARGS_((double coord, double spacing)); -CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc, +static CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc, Tcl_Obj *CONST *objv)); static void InitCanvas _ANSI_ARGS_((void)); #ifdef USE_OLD_TAG_SEARCH @@ -5494,7 +5494,7 @@ CanvasSetOrigin(canvasPtr, xOrigin, yOrigin) *---------------------------------------------------------------------- */ /* ARGSUSED */ -CONST char ** +static CONST char ** TkGetStringsFromObjs(argc, objv) int argc; Tcl_Obj *CONST objv[]; diff --git a/generic/tkText.c b/generic/tkText.c index 4ec38d7..b0b6a21 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -2,10 +2,10 @@ * tkText.c -- * * This module provides a big chunk of the implementation of - * multi-line editable text widgets for Tk. Among other things, - * it provides the Tcl command interfaces to text widgets and - * the display code. The B-tree representation of text is - * implemented elsewhere. + * multi-line editable text widgets for Tk. Among other things, it + * provides the Tcl command interfaces to text widgets. The B-tree + * representation of text and its actual display are implemented + * elsewhere. * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1996 Sun Microsystems, Inc. @@ -14,7 +14,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.c,v 1.38 2003/05/27 15:35:52 vincentdarley Exp $ + * RCS: @(#) $Id: tkText.c,v 1.39 2003/10/31 09:02:08 vincentdarley Exp $ */ #include "default.h" @@ -61,12 +61,16 @@ static Tk_OptionSpec optionSpecs[] = { DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border), 0, (ClientData) DEF_TEXT_BG_MONO, 0}, {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL, - (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0}, + (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-background", 0}, + {TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor", + "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1, + Tk_Offset(TkText, insertCursorType), 0, 0, 0}, {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth), - 0, 0, 0}, + 0, 0, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor), TK_OPTION_NULL_OK, 0, 0}, @@ -76,7 +80,8 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0}, {TK_OPTION_FONT, "-font", "font", "Font", - DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, 0}, + DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0, 0, 0}, @@ -91,7 +96,7 @@ static Tk_OptionSpec optionSpecs[] = { 0, 0, 0}, {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1, - Tk_Offset(TkText, highlightWidth), 0, 0, 0}, + Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground", DEF_TEXT_INSERT_BG, -1, Tk_Offset(TkText, insertBorder), @@ -112,7 +117,8 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo", DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo), 0, 0, 0}, {TK_OPTION_PIXELS, "-padx", "padX", "Pad", - DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, 0}, + DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_PIXELS, "-pady", "padY", "Pad", DEF_TEXT_PADY, -1, Tk_Offset(TkText, padY), 0, 0, 0}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", @@ -132,29 +138,30 @@ static Tk_OptionSpec optionSpecs[] = { DEF_TEXT_SET_GRID, -1, Tk_Offset(TkText, setGrid), 0, 0, 0}, {TK_OPTION_PIXELS, "-spacing1", "spacing1", "Spacing", DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1), - TK_OPTION_DONT_SET_DEFAULT, 0 , 0 }, + TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, {TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing", DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2), - TK_OPTION_DONT_SET_DEFAULT, 0 , 0 }, + TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, {TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing", DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3), - TK_OPTION_DONT_SET_DEFAULT, 0 , 0 }, + TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, {TK_OPTION_STRING_TABLE, "-state", "state", "State", DEF_TEXT_STATE, -1, Tk_Offset(TkText, state), 0, (ClientData) stateStrings, 0}, {TK_OPTION_STRING, "-tabs", "tabs", "Tabs", DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1, - TK_OPTION_NULL_OK, 0, 0}, + TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus), TK_OPTION_NULL_OK, 0, 0}, {TK_OPTION_BOOLEAN, "-undo", "undo", "Undo", DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo), 0, 0 , 0}, {TK_OPTION_INT, "-width", "width", "Width", - DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, 0}, + DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, + TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_STRING_TABLE, "-wrap", "wrap", "Wrap", DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode), - 0, (ClientData) wrapStrings, 0}, + 0, (ClientData) wrapStrings, TK_TEXT_LINE_GEOMETRY}, {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", DEF_TEXT_XSCROLL_COMMAND, -1, Tk_Offset(TkText, xScrollCmd), TK_OPTION_NULL_OK, 0, 0}, @@ -195,6 +202,14 @@ typedef struct SearchSpec { int noCase; /* Case-insenstivive? */ int noLineStop; /* If not set, a regexp search will * use the TCL_REG_NLSTOP flag */ + int overlap; /* If set, results from multiple + * searches (-all) are allowed to + * overlap each other. */ + int strictLimits; /* If set, matches must be + * completely inside the from,to + * range. Otherwise the limits + * only apply to the start of each + * match. */ int all; /* Whether all or the first match should * be reported */ int startLine; /* First line to examine */ @@ -248,12 +263,16 @@ int tkTextDebug = 0; static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int objc, Tcl_Obj *CONST objv[])); static int DeleteChars _ANSI_ARGS_((TkText *textPtr, - Tcl_Obj *index1Obj, Tcl_Obj *index2Obj, CONST TkTextIndex *indexPtr1, - CONST TkTextIndex *indexPtr2)); -static void DestroyText _ANSI_ARGS_((char *memPtr)); + CONST TkTextIndex *indexPtr2, int noViewUpdate)); +static int CountIndices _ANSI_ARGS_((CONST TkText *textPtr, + CONST TkTextIndex *indexPtr1, + CONST TkTextIndex *indexPtr2, + TkTextCountType type)); +static void DestroyText _ANSI_ARGS_((TkText *textPtr)); static int InsertChars _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr, Tcl_Obj *stringPtr)); + TkTextIndex *indexPtr, Tcl_Obj *stringPtr, + int noViewUpdate)); static void TextBlinkProc _ANSI_ARGS_((ClientData clientData)); static void TextCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); @@ -263,6 +282,16 @@ static int TextFetchSelection _ANSI_ARGS_((ClientData clientData, int offset, char *buffer, int maxBytes)); static int TextIndexSortProc _ANSI_ARGS_((CONST VOID *first, CONST VOID *second)); +static int TextInsertCmd _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, + int objc, Tcl_Obj *CONST objv[], + CONST TkTextIndex *indexPtr, int noViewUpdate)); +static int TextReplaceCmd _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, + CONST TkTextIndex *indexFromPtr, + CONST TkTextIndex *indexToPtr, + int objc, Tcl_Obj *CONST objv[], + int noViewUpdate)); static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); @@ -272,8 +301,10 @@ static int TextEditCmd _ANSI_ARGS_((TkText *textPtr, static int TextWidgetObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); -static void TextWorldChanged _ANSI_ARGS_(( +static void TextWorldChangedCallback _ANSI_ARGS_(( ClientData instanceData)); +static void TextWorldChanged _ANSI_ARGS_((TkText *textPtr, + int mask)); static int TextDumpCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); @@ -281,13 +312,15 @@ static void DumpLine _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int what, TkTextLine *linePtr, int start, int end, int lineno, CONST char *command)); -static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, char *key, - char *value, CONST char * command, +static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, + CONST char *key, + CONST char *value, CONST char * command, CONST TkTextIndex *index, int what)); static int TextEditUndo _ANSI_ARGS_((TkText *textPtr)); static int TextEditRedo _ANSI_ARGS_((TkText *textPtr)); -static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkTextIndex * index1, - CONST TkTextIndex * index2)); +static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkText *textPtr, + CONST TkTextIndex * index1, + CONST TkTextIndex * index2, int visibleOnly)); static void UpdateDirtyFlag _ANSI_ARGS_((TkText *textPtr)); static void TextPushUndoAction _ANSI_ARGS_((TkText *textPtr, Tcl_Obj *undoString, int insert, @@ -314,7 +347,7 @@ static SearchLineIndexProc TextSearchGetLineIndex; static Tk_ClassProcs textClass = { sizeof(Tk_ClassProcs), /* size */ - TextWorldChanged, /* worldChangedProc */ + TextWorldChangedCallback, /* worldChangedProc */ }; @@ -387,9 +420,16 @@ Tk_TextObjCmd(clientData, interp, objc, objv) textPtr->relief = TK_RELIEF_FLAT; textPtr->cursor = None; textPtr->charWidth = 1; + textPtr->charHeight = 10; textPtr->wrapMode = TEXT_WRAPMODE_CHAR; textPtr->prevWidth = Tk_Width(new); textPtr->prevHeight = Tk_Height(new); + /* + * This refCount will be held until DestroyText is called. + * Note also that the following call to 'TkTextCreateDInfo' + * will add more refCounts. + */ + textPtr->refCount = 1; TkTextCreateDInfo(textPtr); TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex); TkTextSetYView(textPtr, &startIndex, 0); @@ -402,7 +442,6 @@ Tk_TextObjCmd(clientData, interp, objc, objv) textPtr->lastEditMode = TK_TEXT_EDIT_OTHER; textPtr->tabOptionPtr = NULL; textPtr->stateEpoch = 0; - textPtr->refCount = 1; /* * Create the "sel" tag and the "current" and "insert" marks. @@ -486,16 +525,17 @@ TextWidgetObjCmd(clientData, interp, objc, objv) int index; static CONST char *optionStrings[] = { - "bbox", "cget", "compare", "configure", "debug", "delete", - "dlineinfo", "dump", "edit", "get", "image", "index", - "insert", "mark", "scan", "search", "see", "tag", - "window", "xview", "yview", (char *) NULL + "bbox", "cget", "compare", "configure", "count", "debug", + "delete", "dlineinfo", "dump", "edit", "get", "image", "index", + "insert", "mark", "replace", "scan", "search", "see", + "tag", "window", "xview", "yview", (char *) NULL }; enum options { - TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_DEBUG, - TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, TEXT_GET, - TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK, TEXT_SCAN, - TEXT_SEARCH, TEXT_SEE, TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW + 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_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE, + TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW }; if (objc < 2) { @@ -507,7 +547,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) &index) != TCL_OK) { return TCL_ERROR; } - Tcl_Preserve((ClientData) textPtr); + textPtr->refCount++; switch ((enum options) index) { case TEXT_BBOX: { @@ -525,11 +565,19 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (TkTextCharBbox(textPtr, indexPtr, &x, &y, - &width, &height) == 0) { - char buf[TCL_INTEGER_SPACE * 4]; + &width, &height, NULL) == 0) { + Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); + + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(x)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(y)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(width)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(height)); - sprintf(buf, "%d %d %d %d", x, y, width, height); - Tcl_SetResult(interp, buf, TCL_VOLATILE); + Tcl_SetObjResult(interp, listObj); } break; } @@ -616,6 +664,176 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } break; } + case TEXT_COUNT: { + CONST TkTextIndex *indexFromPtr, *indexToPtr; + int i, found = 0, update = 0; + Tcl_Obj *objPtr = NULL; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, "?options? index1 index2"); + result = TCL_ERROR; + goto done; + } + + indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-2]); + if (indexFromPtr == NULL) { + result = TCL_ERROR; + goto done; + } + indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-1]); + if (indexToPtr == NULL) { + result = TCL_ERROR; + goto done; + } + + for (i = 2; i < objc-2; i++) { + int value; + int length; + CONST char *option = Tcl_GetStringFromObj(objv[i],&length); + char c; + if (length < 2 || option[0] != '-') { + badOption: + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "bad option \"", + Tcl_GetString(objv[i]), + "\" must be -chars, -displaychars, -displayindices, ", + "-displaylines, -indices, -lines, -update, ", + "-xpixels, or -ypixels", NULL); + result = TCL_ERROR; + goto done; + } + c = option[1]; + if (c == 'c' && !strncmp("-chars",option,length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_CHARS); + } else if (c == 'd' && !strncmp("-displaychars", option, + length) && (length > 8)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_DISPLAY_CHARS); + } else if (c == 'd' && !strncmp("-displayindices", option, + length) && (length > 8)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_DISPLAY_INDICES); + } else if (c == 'd' && !strncmp("-displaylines", option, + length) && (length > 8)) { + TkTextLine *fromPtr, *lastPtr; + TkTextIndex index; + + int compare = TkTextIndexCmp(indexFromPtr, indexToPtr); + value = 0; + + if (compare == 0) goto countDone; + + if (compare > 0) { + CONST TkTextIndex *tmpPtr = indexFromPtr; + indexFromPtr = indexToPtr; + indexToPtr = tmpPtr; + } + + lastPtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + fromPtr = indexFromPtr->linePtr; + if (fromPtr == lastPtr) { + goto countDone; + } + + /* + * Caution: we must NEVER call TkTextUpdateOneLine + * with the last artificial line in the widget. + */ + while (fromPtr != indexToPtr->linePtr) { + value += TkTextUpdateOneLine(textPtr, fromPtr); + fromPtr = TkBTreeNextLine(fromPtr); + } + /* + * Now we need to adjust the count to add on the + * number of display lines in the last logical line, + * and subtract off the number of display lines + * overcounted in the first logical line. This logic + * is still ok if both indices are in the same + * logical line. + */ + index.linePtr = indexFromPtr->linePtr; + index.byteIndex = 0; + while (1) { + TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL); + if (index.byteIndex >= indexFromPtr->byteIndex) { + break; + } + TkTextIndexForwBytes(&index, 1, &index); + value--; + } + if (indexToPtr->linePtr != lastPtr) { + index.linePtr = indexToPtr->linePtr; + index.byteIndex = 0; + while (1) { + TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL); + if (index.byteIndex >= indexToPtr->byteIndex) { + break; + } + TkTextIndexForwBytes(&index, 1, &index); + value++; + } + } + + if (compare > 0) { + value = -value; + } + } else if (c == 'i' && !strncmp("-indices",option,length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_INDICES); + } else if (c == 'l' && !strncmp("-lines",option,length)) { + value = TkBTreeLineIndex(indexToPtr->linePtr) + - TkBTreeLineIndex(indexFromPtr->linePtr); + } else if (c == 'u' && !strncmp("-update",option,length)) { + update = 1; + continue; + } else if (c == 'x' && !strncmp("-xpixels",option,length)) { + int x1, x2; + TkTextIndex index; + index = *indexFromPtr; + TkTextFindDisplayLineEnd(textPtr, &index, 0, &x1); + index = *indexToPtr; + TkTextFindDisplayLineEnd(textPtr, &index, 0, &x2); + value = x2 - x1; + } else if (c == 'y' && !strncmp("-ypixels",option,length)) { + if (update) { + TkTextUpdateLineMetrics(textPtr, + TkBTreeLineIndex(indexFromPtr->linePtr), + TkBTreeLineIndex(indexToPtr->linePtr), -1); + } + value = TkTextIndexYPixels(textPtr, indexToPtr) + - TkTextIndexYPixels(textPtr, indexFromPtr); + } else { + goto badOption; + } + countDone: + found++; + if (found == 1) { + Tcl_SetObjResult(interp, Tcl_NewIntObj(value)); + } else { + if (found == 2) { + /* + * Move the first item we put into the result into + * the first element of the list object. + */ + objPtr = Tcl_NewObj(); + Tcl_ListObjAppendElement(NULL, objPtr, + Tcl_GetObjResult(interp)); + } + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewIntObj(value)); + } + } + if (found == 0) { + /* Use the default '-indices' */ + int value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_INDICES); + Tcl_SetObjResult(interp, Tcl_NewIntObj(value)); + } else if (found > 1) { + Tcl_SetObjResult(interp, objPtr); + } + break; + } case TEXT_DEBUG: { if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "boolean"); @@ -645,8 +863,29 @@ TextWidgetObjCmd(clientData, interp, objc, objv) /* * Simple case requires no predetermination of indices. */ - result = DeleteChars(textPtr, objv[2], - (objc == 4) ? objv[3] : NULL, NULL, NULL); + CONST TkTextIndex *indexPtr1, *indexPtr2; + + /* + * Parse the starting and stopping indices. + */ + + indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, + textPtr, objv[2]); + if (indexPtr1 == NULL) { + result = TCL_ERROR; + goto done; + } + if (objc == 4) { + indexPtr2 = TkTextGetIndexFromObj(textPtr->interp, + textPtr, objv[3]); + if (indexPtr2 == NULL) { + result = TCL_ERROR; + goto done; + } + } else { + indexPtr2 = NULL; + } + DeleteChars(textPtr, indexPtr1, indexPtr2, 0); } else { int i; /* @@ -683,7 +922,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) */ if (objc & 1) { indices[i] = indices[i-1]; - TkTextIndexForwChars(&indices[i], 1, &indices[i]); + TkTextIndexForwChars(&indices[i], 1, &indices[i], + COUNT_INDICES); objc++; } useIdx = (char *) ckalloc((unsigned) objc); @@ -740,8 +980,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * We don't need to check the return value * because all indices are preparsed above. */ - DeleteChars(textPtr, NULL, NULL, - &indices[i], &indices[i+1]); + DeleteChars(textPtr, &indices[i], + &indices[i+1], 0); } } ckfree((char *) indices); @@ -765,10 +1005,20 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width, &height, &base) == 0) { - char buf[TCL_INTEGER_SPACE * 5]; + Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); - sprintf(buf, "%d %d %d %d %d", x, y, width, height, base); - Tcl_SetResult(interp, buf, TCL_VOLATILE); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(x)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(y)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(width)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(height)); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewIntObj(base)); + + Tcl_SetObjResult(interp, listObj); } break; } @@ -782,14 +1032,37 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } case TEXT_GET: { Tcl_Obj *objPtr = NULL; - int i, found = 0; - + int i, found = 0, visible = 0; + CONST char *name; + int length; + if (objc < 3) { - Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?"); + Tcl_WrongNumArgs(interp, 2, objv, + "?-displaychars? ?--? index1 ?index2 ...?"); result = TCL_ERROR; goto done; } - for (i = 2; i < objc; i += 2) { + + /* + * Simple, restrictive argument parsing. The only options are -- + * and -displaychars (or any unique prefix). + */ + i = 2; + if (objc > 3) { + name = Tcl_GetStringFromObj(objv[i], &length); + if (length > 1 && name[0] == '-') { + if (strncmp("-displaychars", name, length) == 0) { + i++; + visible = 1; + name = Tcl_GetStringFromObj(objv[i], &length); + } + if ((i < objc-1) && (length == 2) + && (strcmp("--", name) == 0)) { + i++; + } + } + } + for (; i < objc; i += 2) { CONST TkTextIndex *index1Ptr, *index2Ptr; TkTextIndex index2; @@ -802,7 +1075,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (i+1 == objc) { - TkTextIndexForwChars(index1Ptr, 1, &index2); + TkTextIndexForwChars(index1Ptr, 1, &index2, COUNT_INDICES); index2Ptr = &index2; } else { index2Ptr = TkTextGetIndexFromObj(interp, textPtr, @@ -822,7 +1095,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * be a megabyte or more, we want to do it * efficiently! */ - Tcl_Obj *get = TextGetText(index1Ptr, index2Ptr); + Tcl_Obj *get = TextGetText(textPtr, index1Ptr, + index2Ptr, visible); found++; if (found == 1) { Tcl_SetObjResult(interp, get); @@ -881,51 +1155,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (textPtr->state == TK_TEXT_STATE_NORMAL) { - TkTextIndex index1, index2; - int j; - - index1 = *indexPtr; - for (j = 3; j < objc; j += 2) { - /* - * Here we rely on this call to modify index1 if - * it is outside the acceptable range. In particular, - * if index1 is "end", it must be set to the last - * allowable index for insertion, otherwise - * subsequent tag insertions will fail. - */ - int length = InsertChars(textPtr, &index1, objv[j]); - if (objc > (j+1)) { - Tcl_Obj **tagNamePtrs; - TkTextTag **oldTagArrayPtr; - int numTags; - - TkTextIndexForwBytes(&index1, length, &index2); - oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags); - if (oldTagArrayPtr != NULL) { - int i; - for (i = 0; i < numTags; i++) { - TkBTreeTag(&index1, &index2, - oldTagArrayPtr[i], 0); - } - ckfree((char *) oldTagArrayPtr); - } - if (Tcl_ListObjGetElements(interp, objv[j+1], - &numTags, &tagNamePtrs) - != TCL_OK) { - result = TCL_ERROR; - goto done; - } else { - int i; - - for (i = 0; i < numTags; i++) { - TkBTreeTag(&index1, &index2, - TkTextCreateTag(textPtr, - Tcl_GetString(tagNamePtrs[i])), 1); - } - index1 = index2; - } - } - } + result = TextInsertCmd(textPtr, interp, objc-3, objv+3, + indexPtr, 0); } break; } @@ -933,6 +1164,113 @@ TextWidgetObjCmd(clientData, interp, objc, objv) result = TkTextMarkCmd(textPtr, interp, objc, objv); break; } + case TEXT_REPLACE: { + CONST TkTextIndex *indexFromPtr, *indexToPtr; + + if (objc < 5) { + Tcl_WrongNumArgs(interp, 2, objv, + "index1 index2 chars ?tagList chars tagList ...?"); + result = TCL_ERROR; + goto done; + } + indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + if (indexFromPtr == NULL) { + result = TCL_ERROR; + goto done; + } + indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]); + if (indexToPtr == NULL) { + result = TCL_ERROR; + goto done; + } + if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) { + Tcl_AppendResult(interp, "Index \"", Tcl_GetString(objv[3]), + "\" before \"", Tcl_GetString(objv[2]), + "\" in the text.", NULL); + result = TCL_ERROR; + goto done; + } + if (textPtr->state == TK_TEXT_STATE_NORMAL) { + int lineNum, byteIndex; + TkTextIndex index; + /* + * The 'replace' operation is quite complex to do + * correctly, because we want a number of criteria + * to hold: + * + * 1. The insertion point shouldn't move, unless + * it is within the deleted range. In this case + * it should end up after the new text. + * + * 2. The window should not change the text it + * shows -- should not scroll vertically -- unless + * the result of the replace is that the insertion + * position which used to be on-screen is now + * off-screen. + */ + byteIndex = textPtr->topIndex.byteIndex; + lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); + + TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, + &index); + if ((TkTextIndexCmp(indexFromPtr, &index) < 0) + && (TkTextIndexCmp(indexToPtr, &index) > 0)) { + /* + * The insertion point is inside the range to be + * replaced, so we have to do some calculations to + * ensure it doesn't move unnecessarily. + */ + int deleteInsertOffset, insertLength, j; + + insertLength = 0; + for (j = 4; j < objc; j += 2) { + insertLength += Tcl_GetCharLength(objv[j]); + } + + /* + * Calculate 'deleteInsertOffset' as an offset we + * will apply to the insertion point after this + * operation. + */ + deleteInsertOffset = CountIndices(textPtr, indexFromPtr, + &index, COUNT_CHARS); + if (deleteInsertOffset > insertLength) { + deleteInsertOffset = insertLength; + } + + result = TextReplaceCmd(textPtr, interp, + indexFromPtr, indexToPtr, + objc, objv, 1); + + if (result == TCL_OK) { + /* + * Move the insertion position to the correct + * place + */ + TkTextIndexForwChars(indexFromPtr, deleteInsertOffset, + &index, COUNT_INDICES); + TkBTreeUnlinkSegment(textPtr->tree, + textPtr->insertMarkPtr, + textPtr->insertMarkPtr->body.mark.linePtr); + TkBTreeLinkSegment(textPtr->insertMarkPtr, &index); + } + } else { + result = TextReplaceCmd(textPtr, interp, + indexFromPtr, indexToPtr, + objc, objv, 0); + } + if (result == TCL_OK) { + /* + * Now ensure the top-line is in the right + * place + */ + TkTextMakeByteIndex(textPtr->tree, lineNum, + byteIndex, &index); + TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST); + } + } + break; + } case TEXT_SCAN: { result = TkTextScanCmd(textPtr, interp, objc, objv); break; @@ -964,7 +1302,68 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } done: - Tcl_Release((ClientData) textPtr); + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TextReplaceCmd -- + * + * This procedure is invoked to process part of the "replace" widget + * command for text widgets. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +static int +TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr, + objc, objv, noViewUpdate) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + CONST TkTextIndex *indexFromPtr;/* Index from which to replace */ + CONST TkTextIndex *indexToPtr; /* Index to which to replace */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ + int noViewUpdate; /* Don't update the view if set */ +{ + int result; + /* + * Perform the deletion and insertion, but ensure + * no undo-separator is placed between the two + * operations. Since we are using the helper procedures + * 'DeleteChars' and 'TextInsertCmd' we have to pretend + * that the autoSeparators setting is off, so that we don't + * get an undo-separator between the delete and insert. + */ + int origAutoSep = textPtr->autoSeparators; + + if (textPtr->undo) { + textPtr->autoSeparators = 0; + if (origAutoSep && textPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) { + TkUndoInsertUndoSeparator(textPtr->undoStack); + } + } + + DeleteChars(textPtr, indexFromPtr, indexToPtr, noViewUpdate); + result = TextInsertCmd(textPtr, interp, objc-4, objv+4, + indexFromPtr, noViewUpdate); + + if (textPtr->undo) { + textPtr->lastEditMode = TK_TEXT_EDIT_REPLACE; + textPtr->autoSeparators = origAutoSep; + } + return result; } @@ -1016,24 +1415,28 @@ TextIndexSortProc(first, second) * * DestroyText -- * - * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release - * to clean up the internal structure of a text at a safe time - * (when no-one is using it anymore). + * This procedure is invoked when we receive a destroy event + * to clean up the internal structure of a text widget. We will + * free up most of the internal structure and delete the + * associated Tcl command. If there are no outstanding + * references to the widget, we also free up the textPtr itself. + * + * The widget has already been flagged as deleted. * * Results: * None. * * Side effects: - * Everything associated with the text is freed up. + * Either everything or almost everything associated with the + * text is freed up. * *---------------------------------------------------------------------- */ static void -DestroyText(memPtr) - char *memPtr; /* Info about text widget. */ +DestroyText(textPtr) + TkText *textPtr; /* Info about text widget. */ { - register TkText *textPtr = (TkText *) memPtr; Tcl_HashSearch search; Tcl_HashEntry *hPtr; TkTextTag *tagPtr; @@ -1048,6 +1451,8 @@ DestroyText(memPtr) */ TkTextFreeDInfo(textPtr); + textPtr->dInfoPtr = NULL; + TkBTreeDestroy(textPtr->tree); for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { @@ -1111,9 +1516,10 @@ ConfigureText(interp, textPtr, objc, objv) { Tk_SavedOptions savedOptions; int oldExport = textPtr->exportSelection; + int mask = 0; if (Tk_SetOptions(interp, (char*)textPtr, textPtr->optionTable, - objc, objv, textPtr->tkwin, &savedOptions, NULL) != TCL_OK) { + objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) { return TCL_ERROR; } @@ -1173,27 +1579,31 @@ ConfigureText(interp, textPtr, objc, objv) } textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr; textPtr->selTagPtr->affectsDisplay = 0; - if ((textPtr->selTagPtr->border != NULL) - || (textPtr->selTagPtr->borderWidth != 0) - || (textPtr->selTagPtr->reliefString != NULL) - || (textPtr->selTagPtr->bgStipple != None) - || (textPtr->selTagPtr->fgColor != NULL) + textPtr->selTagPtr->affectsDisplay = 0; + textPtr->selTagPtr->affectsDisplayGeometry = 0; + if ((textPtr->selTagPtr->elideString != NULL) || (textPtr->selTagPtr->tkfont != None) - || (textPtr->selTagPtr->fgStipple != None) || (textPtr->selTagPtr->justifyString != NULL) || (textPtr->selTagPtr->lMargin1String != NULL) || (textPtr->selTagPtr->lMargin2String != NULL) || (textPtr->selTagPtr->offsetString != NULL) - || (textPtr->selTagPtr->overstrikeString != NULL) || (textPtr->selTagPtr->rMarginString != NULL) || (textPtr->selTagPtr->spacing1String != NULL) || (textPtr->selTagPtr->spacing2String != NULL) || (textPtr->selTagPtr->spacing3String != NULL) || (textPtr->selTagPtr->tabStringPtr != NULL) - || (textPtr->selTagPtr->underlineString != NULL) - || (textPtr->selTagPtr->elideString != NULL) || (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) { textPtr->selTagPtr->affectsDisplay = 1; + textPtr->selTagPtr->affectsDisplayGeometry = 1; + } + if ((textPtr->selTagPtr->border != NULL) + || (textPtr->selTagPtr->reliefString != NULL) + || (textPtr->selTagPtr->bgStipple != None) + || (textPtr->selTagPtr->fgColor != NULL) + || (textPtr->selTagPtr->fgStipple != None) + || (textPtr->selTagPtr->overstrikeString != NULL) + || (textPtr->selTagPtr->underlineString != NULL)) { + textPtr->selTagPtr->affectsDisplay = 1; } TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, textPtr->selTagPtr, 1); @@ -1241,14 +1651,14 @@ ConfigureText(interp, textPtr, objc, objv) textPtr->height = 1; } Tk_FreeSavedOptions(&savedOptions); - TextWorldChanged((ClientData) textPtr); + TextWorldChanged(textPtr, mask); return TCL_OK; } /* *--------------------------------------------------------------------------- * - * TextWorldChanged -- + * TextWorldChangedCallback -- * * This procedure is called when the world has changed in some * way and the widget needs to recompute all its graphics contexts @@ -1266,19 +1676,53 @@ ConfigureText(interp, textPtr, objc, objv) */ static void -TextWorldChanged(instanceData) +TextWorldChangedCallback(instanceData) ClientData instanceData; /* Information about widget. */ { TkText *textPtr; - Tk_FontMetrics fm; textPtr = (TkText *) instanceData; + TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY); +} + +/* + *--------------------------------------------------------------------------- + * + * TextWorldChanged -- + * + * This procedure is called when the world has changed in some + * way and the widget needs to recompute all its graphics contexts + * and determine its new geometry. + * + * Results: + * None. + * + * Side effects: + * Configures all tags in the Text with a empty objc/objv, for + * the side effect of causing all the items to recompute their + * geometry and to be redisplayed. + * + *--------------------------------------------------------------------------- + */ + +static void +TextWorldChanged(textPtr, mask) + TkText *textPtr; /* Information about widget. */ + int mask; /* OR'd collection of bits showing what + * has changed */ +{ + Tk_FontMetrics fm; textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1); if (textPtr->charWidth <= 0) { textPtr->charWidth = 1; } Tk_GetFontMetrics(textPtr->tkfont, &fm); + + textPtr->charHeight = fm.linespace; + if (textPtr->charHeight <= 0) { + textPtr->charHeight = 1; + } Tk_GeometryRequest(textPtr->tkwin, textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth + 2*textPtr->padX + 2*textPtr->highlightWidth, @@ -1289,12 +1733,12 @@ TextWorldChanged(instanceData) textPtr->borderWidth + textPtr->highlightWidth); if (textPtr->setGrid) { Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height, - textPtr->charWidth, fm.linespace); + textPtr->charWidth, textPtr->charHeight); } else { Tk_UnsetGrid(textPtr->tkwin); } - TkTextRelayoutWindow(textPtr); + TkTextRelayoutWindow(textPtr, mask); } /* @@ -1331,7 +1775,11 @@ TextEventProc(clientData, eventPtr) } else if (eventPtr->type == ConfigureNotify) { if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin)) || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) { - TkTextRelayoutWindow(textPtr); + int mask = 0; + if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)) { + mask = 1; + } + TkTextRelayoutWindow(textPtr, mask); textPtr->prevWidth = Tk_Width(textPtr->tkwin); textPtr->prevHeight = Tk_Height(textPtr->tkwin); } @@ -1360,13 +1808,13 @@ TextEventProc(clientData, eventPtr) textPtr->flags |= DESTROYED; /* - * We don't delete the associated Tcl command yet, because - * that will cause textPtr->tkWin to be nulled out, and that - * is needed inside DestroyText to clean up certain tags - * which might have been created (e.g. in the text widget - * styles demo). + * Call 'DestroyTest' to handle the deletion for us. The + * actual textPtr may still exist after this, if there are + * some outstanding references. But we have flagged it + * as DESTROYED just above, so nothing will try to make use + * of it very extensively. */ - Tcl_EventuallyFree((ClientData) textPtr, DestroyText); + DestroyText(textPtr); } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { if (eventPtr->xfocus.detail != NotifyInferior) { Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler); @@ -1385,11 +1833,15 @@ TextEventProc(clientData, eventPtr) TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1); #endif TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); + /* + * While we wish to redisplay, no heights have changed, so + * no need to call TkTextInvalidateLineMetrics + */ TkTextChanged(textPtr, &index, &index2); if (textPtr->highlightWidth > 0) { TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth, - textPtr->highlightWidth); + textPtr->highlightWidth); } } } @@ -1451,18 +1903,22 @@ TextCmdDeletedProc(clientData) * Side effects: * The characters in "stringPtr" get added to the text just before * the character indicated by "indexPtr". + * + * Unless 'noViewUpdate' is set, we may adjust the window + * contents' y-position, and scrollbar setting. * *---------------------------------------------------------------------- */ static int -InsertChars(textPtr, indexPtr, stringPtr) +InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate) TkText *textPtr; /* Overall information about text widget. */ TkTextIndex *indexPtr; /* Where to insert new characters. May be * modified if the index is not valid * for insertion (e.g. if at "end"). */ Tcl_Obj *stringPtr; /* Null-terminated string containing new * information to add to text. */ + int noViewUpdate; /* Don't update the view if set */ { int lineIndex, resetView, offset, length; @@ -1519,7 +1975,7 @@ InsertChars(textPtr, indexPtr, stringPtr) UpdateDirtyFlag(textPtr); - if (resetView) { + if (resetView && !noViewUpdate) { TkTextIndex newTop; TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop); TkTextIndexForwBytes(&newTop, offset, &newTop); @@ -1643,69 +2099,94 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr) /* *---------------------------------------------------------------------- * + * CountIndices -- + * + * This procedure implements most of the functionality of the + * "count" widget command. + * + * Note that 'textPtr' is only used if we need to check for elided + * attributes, i.e. if type is COUNT_DISPLAY_INDICES or + * COUNT_DISPLAY_CHARS. + * + * Results: + * Returns the number of characters in the range. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +CountIndices(textPtr, indexPtr1, indexPtr2, type) + CONST TkText *textPtr; /* Overall information about text widget. */ + CONST TkTextIndex *indexPtr1;/* Index describing location of first + * character to delete. */ + CONST TkTextIndex *indexPtr2;/* Index describing location of last + * character to delete. NULL means just + * delete the one character given by + * indexPtr1. */ + TkTextCountType type; /* The kind of indices to count */ +{ + /* + * Order the starting and stopping indices. + */ + + int compare = TkTextIndexCmp(indexPtr1, indexPtr2); + + if (compare == 0) { + return 0; + } else if (compare > 0) { + return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type); + } else { + return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type); + } +} + +/* + *---------------------------------------------------------------------- + * * DeleteChars -- * * This procedure implements most of the functionality of the * "delete" widget command. * * Results: - * Returns a standard Tcl result, and leaves an error message - * in textPtr->interp if there is an error. + * Returns a standard Tcl result, currently always TCL_OK. * * Side effects: * Characters get deleted from the text. + * + * Unless 'noViewUpdate' is set, we may adjust the window + * contents' y-position, and scrollbar setting. * *---------------------------------------------------------------------- */ static int -DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) +DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) TkText *textPtr; /* Overall information about text widget. */ - Tcl_Obj *index1Obj; /* Object describing location of first - * character to delete. */ - Tcl_Obj *index2Obj; /* Object describing location of last - * character to delete. NULL means just - * delete the one character given by - * index1Obj. */ CONST TkTextIndex *indexPtr1;/* Index describing location of first * character to delete. */ CONST TkTextIndex *indexPtr2;/* Index describing location of last * character to delete. NULL means just * delete the one character given by * indexPtr1. */ + int noViewUpdate; /* Don't update the view if set */ { int line1, line2, line, byteIndex, resetView; TkTextIndex index1, index2; /* - * Parse the starting and stopping indices. + * Prepare the starting and stopping indices. */ - if (index1Obj != NULL) { - indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr, index1Obj); - if (indexPtr1 == NULL) { - return TCL_ERROR; - } - index1 = *indexPtr1; - if (index2Obj != NULL) { - indexPtr2 = TkTextGetIndexFromObj(textPtr->interp, textPtr, - index2Obj); - if (indexPtr2 == NULL) { - return TCL_ERROR; - } - index2 = *indexPtr2; - } else { - index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); - } + index1 = *indexPtr1; + if (indexPtr2 != NULL) { + index2 = *indexPtr2; } else { - index1 = *indexPtr1; - if (indexPtr2 != NULL) { - index2 = *indexPtr2; - } else { - index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); - } + index2 = index1; + TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES); } /* @@ -1736,10 +2217,10 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) TkTextIndex oldIndex2; oldIndex2 = index2; - TkTextIndexBackChars(&oldIndex2, 1, &index2); + TkTextIndexBackChars(&oldIndex2, 1, &index2, COUNT_INDICES); line2--; if ((index1.byteIndex == 0) && (line1 != 0)) { - TkTextIndexBackChars(&index1, 1, &index1); + TkTextIndexBackChars(&index1, 1, &index1, COUNT_INDICES); line1--; } arrayPtr = TkBTreeGetTags(&index2, &arraySize); @@ -1751,6 +2232,54 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) } } + if (line1 < line2) { + /* + * We are deleting more than one line. For speed, + * we remove all tags from the range first. If we + * don't do this, the code below can (when there are + * many tags) grow non-linearly in execution time. + */ + Tcl_HashSearch search; + Tcl_HashEntry *hPtr; + int i; + + for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); + hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { + TkTextTag *tagPtr; + tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); + if (TkBTreeTag(&index1, &index2, tagPtr, 0)) { + /* + * If the tag is "sel", and we actually adjusted anything + * then grab the selection if we're supposed to export it + * and don't already have it. Also, invalidate + * partially-completed selection retrievals. + * + * This code copied from tkTextTag.c's 'tag remove' + */ + + if (tagPtr == textPtr->selTagPtr) { + XEvent event; + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + + textPtr->abortSelections = 1; + } + } + } + } + /* * Tell the display what's about to happen so it can discard * obsolete display information, then do the deletion. Also, @@ -1815,14 +2344,15 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2) textPtr->lastEditMode = TK_TEXT_EDIT_DELETE; - get = TextGetText(&index1, &index2); + get = TextGetText(textPtr, &index1, &index2, 0); TextPushUndoAction(textPtr, get, 0, &index1, &index2); } UpdateDirtyFlag(textPtr); textPtr->stateEpoch ++; TkBTreeDeleteChars(&index1, &index2); - if (resetView) { + + if (resetView && !noViewUpdate) { TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1); TkTextSetYView(textPtr, &index1, 0); } @@ -2061,7 +2591,7 @@ TextBlinkProc(clientData) { register TkText *textPtr = (TkText *) clientData; TkTextIndex index; - int x, y, w, h; + int x, y, w, h, charWidth; if ((textPtr->state == TK_TEXT_STATE_DISABLED) || !(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) { @@ -2077,15 +2607,98 @@ TextBlinkProc(clientData) textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr); } TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h) == 0) { - TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y, - textPtr->insertWidth, h); + if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h, &charWidth) == 0) { + if (textPtr->insertCursorType) { + /* Block cursor */ + TkTextRedrawRegion(textPtr, x - textPtr->width / 2, y, + charWidth + textPtr->insertWidth / 2, h); + } else { + /* I-beam cursor */ + TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y, + textPtr->insertWidth, h); + } } } /* *---------------------------------------------------------------------- * + * TextInsertCmd -- + * + * This procedure is invoked to process the "insert" and "replace" + * widget commands for text widgets. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + * Unless 'noViewUpdate' is set, we may adjust the window + * contents' y-position, and scrollbar setting. + * + *---------------------------------------------------------------------- + */ + +static int +TextInsertCmd(textPtr, interp, objc, objv, indexPtr, noViewUpdate) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ + CONST TkTextIndex *indexPtr;/* Index at which to insert */ + int noViewUpdate; /* Don't update the view if set */ +{ + TkTextIndex index1, index2; + int j; + + index1 = *indexPtr; + for (j = 0; j < objc; j += 2) { + /* + * Here we rely on this call to modify index1 if + * it is outside the acceptable range. In particular, + * if index1 is "end", it must be set to the last + * allowable index for insertion, otherwise + * subsequent tag insertions will fail. + */ + int length = InsertChars(textPtr, &index1, objv[j], noViewUpdate); + if (objc > (j+1)) { + Tcl_Obj **tagNamePtrs; + TkTextTag **oldTagArrayPtr; + int numTags; + + TkTextIndexForwBytes(&index1, length, &index2); + oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags); + if (oldTagArrayPtr != NULL) { + int i; + for (i = 0; i < numTags; i++) { + TkBTreeTag(&index1, &index2, + oldTagArrayPtr[i], 0); + } + ckfree((char *) oldTagArrayPtr); + } + if (Tcl_ListObjGetElements(interp, objv[j+1], + &numTags, &tagNamePtrs) + != TCL_OK) { + return TCL_ERROR; + } else { + int i; + + for (i = 0; i < numTags; i++) { + TkBTreeTag(&index1, &index2, + TkTextCreateTag(textPtr, + Tcl_GetString(tagNamePtrs[i])), 1); + } + index1 = index2; + } + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * TextSearchCmd -- * * This procedure is invoked to process the "search" widget command @@ -2113,12 +2726,14 @@ TextSearchCmd(textPtr, interp, objc, objv) static CONST char *switchStrings[] = { "--", "-all", "-backwards", "-count", "-elide", "-exact", - "-forwards", "-hidden", "-nocase", "-nolinestop", "-regexp", NULL + "-forwards", "-hidden", "-nocase", "-nolinestop", + "-overlap", "-regexp", "-strictlimits", NULL }; enum SearchSwitches { SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_ELIDE, SEARCH_EXACT, SEARCH_FWD, SEARCH_HIDDEN, SEARCH_NOCASE, - SEARCH_NOLINE, SEARCH_REGEXP + SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP, + SEARCH_STRICTLIMITS }; /* @@ -2134,6 +2749,8 @@ TextSearchCmd(textPtr, interp, objc, objv) searchSpec.resPtr = NULL; searchSpec.searchElide = 0; searchSpec.noLineStop = 0; + searchSpec.overlap = 0; + searchSpec.strictLimits = 0; searchSpec.numLines = TkBTreeNumLines(textPtr->tree); searchSpec.clientData = (ClientData)textPtr; searchSpec.addLineProc = &TextSearchAddNextLine; @@ -2158,7 +2775,8 @@ TextSearchCmd(textPtr, interp, objc, objv) Tcl_ResetResult(interp); Tcl_AppendResult(interp, "bad switch \"", Tcl_GetString(objv[i]), "\": must be --, -all, -backward, -count, -elide, ", - "-exact, -forward, -nocase, -nolinestop, or -regexp", + "-exact, -forward, -nocase, -nolinestop, -overlap, ", + "-regexp, or -strictlimits", (char *) NULL); return TCL_ERROR; } @@ -2199,9 +2817,15 @@ TextSearchCmd(textPtr, interp, objc, objv) case SEARCH_NOCASE: searchSpec.noCase = 1; break; - case SEARCH_NOLINE: + case SEARCH_NOLINESTOP: searchSpec.noLineStop = 1; break; + case SEARCH_OVERLAP: + searchSpec.overlap = 1; + break; + case SEARCH_STRICTLIMITS: + searchSpec.strictLimits = 1; + break; case SEARCH_REGEXP: searchSpec.exact = 0; break; @@ -2224,6 +2848,12 @@ TextSearchCmd(textPtr, interp, objc, objv) return TCL_ERROR; } + if (searchSpec.overlap && !searchSpec.all) { + Tcl_SetResult(interp, "the \"-overlap\" option requires the " + "\"-all\" option to be present", TCL_STATIC); + return TCL_ERROR; + } + /* * Scan through all of the lines of the text circularly, starting * at the given index. 'objv[i]' is the pattern which may be an @@ -2487,12 +3117,17 @@ TextSearchAddNextLine(lineNum, searchSpecPtr, theLine, lenPtr) static int TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, - matchOffset, matchLength) + matchOffset, matchLength) int lineNum; /* Line on which match was found */ SearchSpec *searchSpecPtr; /* Search parameters */ ClientData clientData; /* Token returned by the 'addNextLineProc', - * TextSearchAddNextLine */ - Tcl_Obj *theLine; /* Text from current line */ + * TextSearchAddNextLine. May be + * NULL, in which we case we must + * generate it (from lineNum) */ + Tcl_Obj *theLine; /* Text from current line, only + * accessed for exact searches, and + * is allowed to be NULL for regexp + * searches. */ int matchOffset; /* Offset of found item in utf-8 bytes * for exact search, Unicode chars * for regexp */ @@ -2544,6 +3179,10 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, */ linePtr = (TkTextLine *)clientData; + if (linePtr == NULL) { + linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + } + curIndex.tree = textPtr->tree; curIndex.linePtr = linePtr; curIndex.byteIndex = 0; /* Find the starting point */ @@ -2844,7 +3483,7 @@ TextDumpCmd(textPtr, interp, objc, objv) arg++; atEnd = 0; if (objc == arg) { - TkTextIndexForwChars(&index1, 1, &index2); + TkTextIndexForwChars(&index1, 1, &index2, COUNT_INDICES); } else { int length; char *str; @@ -3008,8 +3647,8 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) static int DumpSegment(interp, key, value, command, index, what) Tcl_Interp *interp; - char *key; /* Segment type key */ - char *value; /* Segment value */ + CONST char *key; /* Segment type key */ + CONST char *value; /* Segment value */ CONST char *command; /* Script callback */ CONST TkTextIndex *index; /* index with line/byte position info */ int what; /* Look for TK_DUMP_INDEX bit */ @@ -3267,7 +3906,10 @@ TextEditCmd(textPtr, interp, objc, objv) * be as simple as replacing Tcl_NewObj by Tcl_NewUnicodeObj. * * Results: - * Tcl_Obj of string type containing the specified text. + * Tcl_Obj of string type containing the specified text. If the + * visibleOnly flag is set to 1, then only those characters which + * are not elided will be returned. Otherwise (flag is 0) all + * characters in the given range are returned. * * Side effects: * Memory will be allocated for the new object. Remember to free it if @@ -3277,9 +3919,12 @@ TextEditCmd(textPtr, interp, objc, objv) */ static Tcl_Obj* -TextGetText(indexPtr1,indexPtr2) - CONST TkTextIndex *indexPtr1; - CONST TkTextIndex *indexPtr2; +TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly) + CONST TkText *textPtr; /* Information about text widget. */ + CONST TkTextIndex *indexPtr1; /* Get text from this index... */ + CONST TkTextIndex *indexPtr2; /* ...to this index */ + int visibleOnly; /* If non-zero, then only return + * non-elided characters. */ { TkTextIndex tmpIndex; Tcl_Obj *resultPtr = Tcl_NewObj(); @@ -3311,8 +3956,10 @@ TextGetText(indexPtr1,indexPtr2) } } if (segPtr->typePtr == &tkTextCharType) { - Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset, - last - offset); + if (!visibleOnly || !TkTextIsElided(textPtr, &tmpIndex)) { + Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset, + last - offset); + } } TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex); } @@ -3423,9 +4070,9 @@ SearchPerform(interp, searchSpecPtr, patObj, fromPtr, toPtr) /* * Scan through all of the lines of the text circularly, starting - * at the given index. 'objv[i]' is the pattern which may be an - * exact string or a regexp pattern depending on the flags set - * above. + * at the given index. 'patObj' is the pattern which may be an + * exact string or a regexp pattern depending on the flags in + * searchSpecPtr. */ return SearchCore(interp, searchSpecPtr, patObj); @@ -3482,10 +4129,27 @@ SearchCore(interp, searchSpecPtr, patObj) int lineNum = searchSpecPtr->startLine; int code = TCL_OK; Tcl_Obj *theLine; - + int alreadySearchOffset = -1; + Tcl_RegExp regexp = NULL; /* For regexp searches only */ - CONST char *pattern = NULL; /* For exact searches only */ - int firstNewLine = -1; /* For exact searches only */ + /* + * These items are for backward regexp searches only. They are for + * two purposes: to allow us to report backwards matches in the + * correct order, even though the implementation uses repeated + * forward searches; and to provide for overlap checking between + * backwards matches on different text lines. + */ +#define LOTS_OF_MATCHES 20 + int matchNum = LOTS_OF_MATCHES; + int smArray[2 * LOTS_OF_MATCHES]; + int *storeMatch = smArray; + int *storeLength = smArray + LOTS_OF_MATCHES; + int lastBackwardsLineMatch = -1; + int lastBackwardsMatchOffset = -1; + + /* These two items are for exact searches only */ + CONST char *pattern = NULL; + int firstNewLine = -1; if (searchSpecPtr->exact) { /* @@ -3521,7 +4185,7 @@ SearchCore(interp, searchSpecPtr, patObj) * will have to search across multiple lines. */ if (searchSpecPtr->exact) { - char *nl; + CONST char *nl; /* * We only need to set the matchLength once for exact searches, @@ -3571,13 +4235,31 @@ SearchCore(interp, searchSpecPtr, patObj) * 'lastOffset' (in bytes if exact, chars if regexp), since * obviously the length is the maximum offset at which * it is possible to find something on this line, which is - * what we 'lastOffset' represents. + * what 'lastOffset' represents. */ lineInfo = (*searchSpecPtr->addLineProc)(lineNum, searchSpecPtr, theLine, &lastOffset); - firstOffset = 0; + if (lineNum == searchSpecPtr->stopLine && searchSpecPtr->backwards) { + firstOffset = searchSpecPtr->stopOffset; + } else { + firstOffset = 0; + } + + if (alreadySearchOffset != -1) { + if (searchSpecPtr->backwards) { + if (alreadySearchOffset < lastOffset) { + lastOffset = alreadySearchOffset; + } + } else { + if (alreadySearchOffset > firstOffset) { + firstOffset = alreadySearchOffset; + } + } + alreadySearchOffset = -1; + } + if (lineNum == searchSpecPtr->startLine) { /* * The starting line is tricky: the first time we see it @@ -3593,18 +4275,21 @@ SearchCore(interp, searchSpecPtr, patObj) * Only use the last part of the line. */ - if ((searchSpecPtr->startOffset >= lastOffset) - && ((lastOffset != 0) || searchSpecPtr->exact)) { + if (searchSpecPtr->startOffset > firstOffset) { + firstOffset = searchSpecPtr->startOffset; + } + if ((firstOffset >= lastOffset) + && ((lastOffset != 0) || searchSpecPtr->exact)) { goto nextLine; } - - firstOffset = searchSpecPtr->startOffset; } else { /* * Use only the first part of the line. */ - lastOffset = searchSpecPtr->startOffset; + if (searchSpecPtr->startOffset < lastOffset) { + lastOffset = searchSpecPtr->startOffset; + } } } @@ -3625,18 +4310,71 @@ SearchCore(interp, searchSpecPtr, patObj) do { Tcl_UniChar ch; CONST char *p; + int lastFullLine = lastOffset; - p = strstr(startOfLine + firstOffset, pattern); - if (p == NULL) { - if (firstNewLine == -1 || - firstNewLine >= (lastOffset - firstOffset)) { + if (firstNewLine == -1) { + if (searchSpecPtr->strictLimits + && (firstOffset + matchLength > lastOffset)) { + /* Not enough characters to match. */ + break; + } + /* + * Single line matching. We want to scan forwards + * or backwards as appropriate. + */ + if (searchSpecPtr->backwards) { + /* + * Search back either from the previous match or + * from 'startOfLine + lastOffset - 1' until we + * find a match. + */ + CONST char c = pattern[0]; + if (alreadySearchOffset != -1) { + p = startOfLine + alreadySearchOffset; + alreadySearchOffset = -1; + } else { + p = startOfLine + lastOffset -1; + } + while (p >= startOfLine + firstOffset) { + if (p[0] == c && !strncmp(p, pattern, matchLength)) { + goto backwardsMatch; + } + p--; + } break; + } else { + p = strstr(startOfLine + firstOffset, pattern); } + if (p == NULL) { + /* + * Single line match failed. + */ + break; + } + } else if (firstNewLine >= (lastOffset - firstOffset)) { + /* + * Multi-line match, but not enough characters to + * match. + */ + break; + } else { + /* + * Multi-line match has only one possible match + * position, because we know where the '\n' is. + */ p = startOfLine + lastOffset - firstNewLine - 1; if (strncmp(p, pattern, (unsigned)(firstNewLine + 1))) { + /* No match */ break; } else { int extraLines = 1; + /* + * If we find a match that overlaps more than one + * line, we will use this value to determine the + * first allowed starting offset for the following + * search (to avoid overlapping results). + */ + int lastTotal = lastOffset; int skipFirst = lastOffset - firstNewLine -1; /* * We may be able to match if given more text. @@ -3644,7 +4382,7 @@ SearchCore(interp, searchSpecPtr, patObj) * exact searches. */ while (1) { - int len; + lastFullLine = lastTotal; if (lineNum+extraLines>=searchSpecPtr->numLines) { p = NULL; @@ -3658,8 +4396,11 @@ SearchCore(interp, searchSpecPtr, patObj) if (extraLines > maxExtraLines) { if ((*searchSpecPtr->addLineProc)(lineNum + extraLines, searchSpecPtr, theLine, - &len) == NULL) { + &lastTotal) == NULL) { p = NULL; + if (!searchSpecPtr->backwards) { + linesSearched = extraLines + 1; + } break; } maxExtraLines = extraLines; @@ -3671,7 +4412,7 @@ SearchCore(interp, searchSpecPtr, patObj) * Use the fact that 'matchLength = patLength' * for exact searches */ - if ((len - skipFirst) >= matchLength) { + if ((lastTotal - skipFirst) >= matchLength) { /* * We now have enough text to match, so * we make a final test and break @@ -3686,7 +4427,7 @@ SearchCore(interp, searchSpecPtr, patObj) * Not enough text yet, but check the prefix */ if (strncmp(p, pattern, - (unsigned)(len - skipFirst))) { + (unsigned)(lastTotal - skipFirst))) { p = NULL; break; } @@ -3704,44 +4445,77 @@ SearchCore(interp, searchSpecPtr, patObj) if (p == NULL) { break; } + linesSearched = extraLines; } } - firstOffset = p - startOfLine; - if (firstOffset >= lastOffset) { + backwardsMatch: + if ((p - startOfLine) >= lastOffset) { break; } - /* * Remember the match */ - matchOffset = firstOffset; + matchOffset = p - startOfLine; - /* - * Move the starting point one character on from the - * previous match, in case we are doing repeated or - * backwards searches (for the latter, we actually do - * repeated forward searches). - */ - firstOffset += Tcl_UtfToUniChar(startOfLine+matchOffset, &ch); if (searchSpecPtr->all && - !(*searchSpecPtr->foundMatchProc)(lineNum, - searchSpecPtr, lineInfo, theLine, matchOffset, - matchLength)) { + !(*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength)) { /* * We reached the end of the search */ goto searchDone; } - } while (searchSpecPtr->backwards || searchSpecPtr->all); + if (!searchSpecPtr->overlap) { + if (searchSpecPtr->backwards) { + alreadySearchOffset = p - startOfLine; + if (firstNewLine != -1) { + break; + } else { + alreadySearchOffset -= matchLength; + } + } else { + firstOffset = p - startOfLine + matchLength; + if (firstOffset >= lastOffset) { + /* + * Now, we have to be careful not to find + * overlapping matches either on the same or + * following lines. Assume that if we did find + * something, it goes until the last extra line + * we added. + * + * We can break out of the loop, since we know + * no more will be found. + */ + if (!searchSpecPtr->backwards) { + alreadySearchOffset = firstOffset - lastFullLine; + break; + } + } + } + } else { + if (searchSpecPtr->backwards) { + alreadySearchOffset = p - startOfLine - 1; + if (alreadySearchOffset < 0) { + break; + } + } else { + firstOffset = p - startOfLine + + Tcl_UtfToUniChar(startOfLine + matchOffset, &ch); + } + } + } while (searchSpecPtr->all); } else { int maxExtraLines = 0; - + int matches = 0; + int lastNonOverlap = -1; + do { Tcl_RegExpInfo info; int match; - + int lastFullLine = lastOffset; + match = Tcl_RegExpExecObj(interp, regexp, theLine, firstOffset, 1, ((firstOffset > 0) ? TCL_REG_NOTBOL : 0)); if (match < 0) { @@ -3750,24 +4524,45 @@ SearchCore(interp, searchSpecPtr, patObj) } Tcl_RegExpGetInfo(regexp, &info); - if (!match) { + /* + * If we don't have a match, or if we do, but it + * extends to the end of the line, we must try to + * add more lines to get a full greedy match. + */ + if (!match + || ((info.extendStart == info.matches[0].start) + && (info.matches[0].end == lastOffset))) { int extraLines = 1; - int curLen = 0; + int prevFullLine; + /* + * If we find a match that overlaps more than one + * line, we will use this value to determine the + * first allowed starting offset for the following + * search (to avoid overlapping results). + */ + int lastTotal = lastOffset; + + if ((lastBackwardsLineMatch != -1) + && (lastBackwardsLineMatch == (lineNum + 1))) { + lastNonOverlap = lastTotal; + } if (info.extendStart < 0) { + /* No multi-line match is possible */ break; } /* * We may be able to match if given more text. * The following 'while' block handles multi-line - * exact searches. + * regexp searches. */ while (1) { + prevFullLine = lastTotal; /* * Move firstOffset to first possible start */ - firstOffset += info.extendStart; + if (!match) firstOffset += info.extendStart; if (firstOffset >= lastOffset) { /* * We're being told that the only possible @@ -3775,8 +4570,8 @@ SearchCore(interp, searchSpecPtr, patObj) * the line. But, that is the next line which * we will handle when we look at that line. */ - if (!searchSpecPtr->backwards - && (firstOffset == curLen)) { + if (!match && !searchSpecPtr->backwards + && (firstOffset == 0)) { linesSearched = extraLines + 1; } break; @@ -3791,17 +4586,22 @@ SearchCore(interp, searchSpecPtr, patObj) if (extraLines > maxExtraLines) { if ((*searchSpecPtr->addLineProc)(lineNum + extraLines, searchSpecPtr, theLine, - NULL) == NULL) { + &lastTotal) == NULL) { /* * There are no more acceptable lines, so * we can say we have searched all of these */ - if (!searchSpecPtr->backwards) { + if (!match && !searchSpecPtr->backwards) { linesSearched = extraLines + 1; } break; } maxExtraLines = extraLines; + if ((lastBackwardsLineMatch != -1) + && (lastBackwardsLineMatch + == (lineNum + extraLines + 1))) { + lastNonOverlap = lastTotal; + } } match = Tcl_RegExpExecObj(interp, regexp, theLine, @@ -3812,64 +4612,286 @@ SearchCore(interp, searchSpecPtr, patObj) goto searchDone; } Tcl_RegExpGetInfo(regexp, &info); - if (match || info.extendStart < 0) { + + /* + * Unfortunately there are bugs in Tcl's regexp + * library, which tells us that info.extendStart + * is zero when it should not be (should be -1), + * which makes our task a bit more complicated + * here. We check if there was a match, and the + * end of the match leaves an entire extra line + * unmatched, then we stop searching. Clearly it + * still might sometimes be possible to add more + * text and match again, but Tcl's regexp library + * doesn't tell us that. + * + * This means we often add and search one more + * line than might be necessary if Tcl were able + * to give us a correct value of info.extendStart + * under all circumstances. + */ + if ((match && ((firstOffset + info.matches[0].end) + != lastTotal) + && ((firstOffset + info.matches[0].end) + < prevFullLine)) + || info.extendStart < 0) { break; } + if (match && ((firstOffset + info.matches[0].end) + >= prevFullLine)) { + linesSearched = extraLines; + lastFullLine = prevFullLine; + } /* * The prefix matches, so keep looking */ extraLines++; } /* - * If we reach here, with match == 1, we've found a + * If we reach here with 'match == 1', we've found a * multi-line match, which we will record in the code * which follows directly else we started a * multi-line match but didn't finish it off, so we * go to the next line. - * - * Here is where we could perform an optimisation, - * since we have already retrieved the contents of - * the next line (and many more), so we shouldn't - * really throw it all away and start again. This - * could be particularly important for complex regexp - * searches. */ if (!match) { - /* - * This 'break' will take us to just before - * the 'nextLine:' below. + /* + * Here is where we could perform an optimisation, + * since we have already retrieved the contents of + * the next line (perhaps many more), so we shouldn't + * really throw it all away and start again. This + * could be particularly important for complex regexp + * searches. + * + * This 'break' will take us to just before the + * 'nextLine:' below. */ break; } - } + if (lastBackwardsLineMatch != -1) { + if ((lineNum + linesSearched) + == lastBackwardsLineMatch) { + /* Possible overlap or inclusion */ + int thisOffset = firstOffset + info.matches[0].end + - info.matches[0].start; + + if (lastNonOverlap != -1) { + /* Possible overlap or enclosure */ + if ((thisOffset - lastNonOverlap) + >= (lastBackwardsMatchOffset + + matchLength)) { + /* + * Totally encloses previous match, so + * forget the previous match + */ + lastBackwardsLineMatch = -1; + } else if ((thisOffset - lastNonOverlap) + > lastBackwardsMatchOffset) { + /* + * Overlap. Previous match is ok, and + * the current match is only ok if + * we are searching with -overlap. + */ + if (searchSpecPtr->overlap) { + goto recordBackwardsMatch; + } else { + match = 0; + break; + } + } else { + /* + * No overlap, although the same + * line was reached. + */ + goto recordBackwardsMatch; + } + } else { + /* No overlap */ + goto recordBackwardsMatch; + } + } else if (lineNum + linesSearched + < lastBackwardsLineMatch) { + /* No overlap */ + goto recordBackwardsMatch; + } else { + /* Totally enclosed */ + lastBackwardsLineMatch = -1; + } + } + + } else { + /* Matched in a single line */ + if (lastBackwardsLineMatch != -1) { + recordBackwardsMatch: + (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, + searchSpecPtr, NULL, NULL, + lastBackwardsMatchOffset, matchLength); + lastBackwardsLineMatch = -1; + if (!searchSpecPtr->all) { + goto searchDone; + } + } + } + firstOffset += info.matches[0].start; if (firstOffset >= lastOffset) { break; } /* - * Remember the match + * Update our local variables with the match, if we + * haven't yet found anything, or if we're doing '-all' + * or '-backwards' _and_ this match isn't fully enclosed + * in the previous match. */ - matchOffset = firstOffset; - matchLength = info.matches[0].end - info.matches[0].start; + + if (matchOffset == -1 || + ((searchSpecPtr->all || searchSpecPtr->backwards) + && ((firstOffset < matchOffset) + || ((firstOffset + info.matches[0].end + - info.matches[0].start) + > (matchOffset + matchLength)) + ) + ) + ) { + + matchOffset = firstOffset; + matchLength = info.matches[0].end - info.matches[0].start; + + if (searchSpecPtr->backwards) { + /* + * To get backwards searches in the correct + * order, we must store them away here. + */ + if (matches == matchNum) { + /* + * We've run out of space in our normal + * store, so we must allocate space for + * these backwards matches on the heap. + */ + int *newArray; + newArray = (int*) ckalloc(4*matchNum *sizeof(int)); + memcpy(newArray, storeMatch, matchNum*sizeof(int)); + memcpy(newArray + 2*matchNum, + storeLength, matchNum*sizeof(int)); + if (storeMatch != smArray) { + ckfree((char*)storeMatch); + } + matchNum *= 2; + storeMatch = newArray; + storeLength = newArray + matchNum; + } + storeMatch[matches] = matchOffset; + storeLength[matches] = matchLength; + matches++; + } else { + /* + * Now actually record the match, but only if we + * are doing an '-all' search. + */ + if (searchSpecPtr->all && + !(*searchSpecPtr->foundMatchProc)(lineNum, + searchSpecPtr, lineInfo, theLine, matchOffset, + matchLength)) { + /* + * We reached the end of the search + */ + goto searchDone; + } + } + /* + * For forward matches, unless we allow overlaps, we + * move this on by the length of the current match so + * that we explicitly disallow overlapping matches. + */ + if (matchLength > 0 && !searchSpecPtr->overlap + && !searchSpecPtr->backwards) { + firstOffset += matchLength; + if (firstOffset >= lastOffset) { + /* + * Now, we have to be careful not to find + * overlapping matches either on the same or + * following lines. Assume that if we did find + * something, it goes until the last extra line + * we added. + * + * We can break out of the loop, since we know + * no more will be found. + */ + alreadySearchOffset = firstOffset - lastFullLine; + break; + } + /* We'll add this on again just below */ + firstOffset --; + } + } /* - * Move the starting point one character on, in case - * we are doing repeated or backwards searches (for the - * latter, we actually do repeated forward searches). + * Move the starting point on, in case we are doing + * repeated or backwards searches (for the latter, we + * actually do repeated forward searches). */ firstOffset++; - if (searchSpecPtr->all && - !(*searchSpecPtr->foundMatchProc)(lineNum, - searchSpecPtr, lineInfo, theLine, matchOffset, - matchLength)) { - /* - * We reached the end of the search + } while (searchSpecPtr->backwards || searchSpecPtr->all); + + if (matches > 0) { + /* + * Now we have all the matches in our array, but not stored + * with 'foundMatchProc' yet. + */ + matches--; + matchOffset = storeMatch[matches]; + matchLength = storeLength[matches]; + while (--matches >= 0) { + if (lineNum == searchSpecPtr->stopLine) { + /* + * It appears as if a condition like + * 'if (storeMatch[matches] + * < searchSpecPtr->stopOffset) break;' + * + * might be needed here, but no test case + * has been found which would exercise such + * a problem. + */ + } + if (storeMatch[matches] + storeLength[matches] + >= matchOffset + matchLength) { + /* + * The new match totally encloses the previous + * one, so we overwrite the previous one. + */ + matchOffset = storeMatch[matches]; + matchLength = storeLength[matches]; + continue; + } + if (!searchSpecPtr->overlap) { + if (storeMatch[matches] + storeLength[matches] + > matchOffset) { + continue; + } + } + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); + if (!searchSpecPtr->all) { + goto searchDone; + } + matchOffset = storeMatch[matches]; + matchLength = storeLength[matches]; + } + if (searchSpecPtr->all && matches > 0) { + /* + * We only need to do this for the '-all' case, + * because just below we will call the + * foundMatchProc for the non-all case */ - goto searchDone; + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); + } else { + lastBackwardsLineMatch = lineNum; + lastBackwardsMatchOffset = matchOffset; } - } while (searchSpecPtr->backwards || searchSpecPtr->all); + } } /* @@ -3880,9 +4902,11 @@ SearchCore(interp, searchSpecPtr, patObj) * and we are done. */ - if ((matchOffset >= 0) && !searchSpecPtr->all) { - (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, - lineInfo, theLine, matchOffset, matchLength); + if ((lastBackwardsLineMatch == -1) + && (matchOffset >= 0) + && !searchSpecPtr->all) { + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); goto searchDone; } @@ -3892,7 +4916,7 @@ SearchCore(interp, searchSpecPtr, patObj) nextLine: - for (; linesSearched>0 ; linesSearched--) { + while (linesSearched-- > 0) { /* * If we have just completed the 'stopLine', we are done */ @@ -3902,26 +4926,71 @@ SearchCore(interp, searchSpecPtr, patObj) if (searchSpecPtr->backwards) { lineNum--; + + if (lastBackwardsLineMatch != -1 + && ((lineNum < 0) || (lineNum + 2 < lastBackwardsLineMatch))) { + (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, + searchSpecPtr, NULL, NULL, + lastBackwardsMatchOffset, + matchLength); + lastBackwardsLineMatch = -1; + if (!searchSpecPtr->all) { + goto searchDone; + } + } + if (lineNum < 0) { lineNum = searchSpecPtr->numLines-1; } + if (!searchSpecPtr->exact) { + /* + * The 'exact' search loops above are designed to + * give us an accurate picture of the number of lines + * which we can skip here. For 'regexp' searches, on + * the other hand, which can match potentially variable + * lengths, we cannot skip multiple lines when searching + * backwards. Therefore we only allow one line to be + * skipped here. + */ + break; + } } else { lineNum++; if (lineNum >= searchSpecPtr->numLines) { lineNum = 0; } } + if (lineNum == searchSpecPtr->startLine && linesSearched > 0) { + /* + * We've just searched all the way round and have + * gone right through the start line without finding + * anything in the last attempt. + */ + break; + } } Tcl_SetObjLength(theLine, 0); } searchDone: + if (lastBackwardsLineMatch != -1) { + (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, searchSpecPtr, + NULL, NULL, lastBackwardsMatchOffset, matchLength); + } + /* * Free up the cached line and pattern */ Tcl_DecrRefCount(theLine); Tcl_DecrRefCount(patObj); + /* + * Free up any extra space we allocated + */ + if (storeMatch != smArray) { + ckfree((char*)storeMatch); + } + return code; } diff --git a/generic/tkText.h b/generic/tkText.h index efe872d..f59f003 100644 --- a/generic/tkText.h +++ b/generic/tkText.h @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkText.h,v 1.16 2003/09/29 23:15:20 dkf Exp $ + * RCS: @(#) $Id: tkText.h,v 1.17 2003/10/31 09:02:09 vincentdarley Exp $ */ #ifndef _TKTEXT @@ -37,8 +37,9 @@ typedef struct TkTextBTree_ *TkTextBTree; /* - * The data structure below defines a single line of text (from newline - * to newline, not necessarily what appears on one line of the screen). + * The data structure below defines a single logical line of text (from + * newline to newline, not necessarily what appears on one display line + * of the screen). */ typedef struct TkTextLine { @@ -49,6 +50,15 @@ typedef struct TkTextLine { * means end of list. */ struct TkTextSegment *segPtr; /* First in ordered list of segments * that make up the line. */ + int pixelHeight; /* The number of vertical + * pixels taken up by this + * line, whether currently + * displayed or not. This + * number is only updated + * asychronously. */ + int pixelCalculationEpoch; /* The last epoch at which the + * pixel height was + * recalculated. */ } TkTextLine; /* @@ -148,6 +158,8 @@ typedef struct TkTextEmbImage { * of image, in pixels. */ int chunkCount; /* Number of display chunks that * refer to this image. */ + Tk_OptionTable optionTable; /* Token representing the + * configuration specifications. */ } TkTextEmbImage; /* @@ -189,7 +201,11 @@ typedef struct TkTextIndex { * of interest. */ int byteIndex; /* Index within line of desired * character (0 means first one). */ - struct TkText *textPtr; + struct TkText *textPtr; /* May be NULL, but otherwise + * the text widget with which + * this index is associated. + * If not NULL, then we have a + * refCount on the widget. */ } TkTextIndex; /* @@ -286,7 +302,7 @@ typedef enum { TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, } TkWrapMode; typedef struct TkTextTag { - char *name; /* Name of this tag. This field is actually + CONST char *name; /* Name of this tag. This field is actually * a pointer to the key from the entry in * textPtr->tagTable, so it needn't be freed * explicitly. */ @@ -395,6 +411,10 @@ typedef struct TkTextTag { * (so need to redisplay if tag changes). */ Tk_OptionTable optionTable; /* Token representing the configuration * specifications. */ + int affectsDisplayGeometry; /* Non-zero means that this tag affects the + * size with which information is displayed + * on the screen (so need to recalculate line + * dimensions if tag changes). */ } TkTextTag; #define TK_TAG_AFFECTS_DISPLAY 0x1 @@ -464,6 +484,7 @@ typedef struct TkTextTabArray { typedef enum { TK_TEXT_EDIT_INSERT, /* insert mode */ TK_TEXT_EDIT_DELETE, /* delete mode */ + TK_TEXT_EDIT_REPLACE, /* replace mode */ TK_TEXT_EDIT_OTHER /* none of the above */ } TkTextEditMode; @@ -537,6 +558,8 @@ typedef struct TkText { Tk_Font tkfont; /* Default font for displaying text. */ int charWidth; /* Width of average character in default * font. */ + int charHeight; /* Height of average character in default + * font, including line spacing. */ int spacing1; /* Default extra spacing above first display * line for each text line. */ int spacing2; /* Default extra spacing between display lines @@ -687,9 +710,10 @@ typedef struct TkText { int isDirtyIncrement; /* Amount with which the isDirty flag is * incremented every edit action */ - TkTextEditMode lastEditMode;/* Keeps track of what the last edit mode was - */ - + TkTextEditMode lastEditMode;/* Keeps track of what the last edit + * mode was */ + int insertCursorType; /* 0 = standard insertion cursor, + * 1 = block cursor. */ } TkText; /* @@ -745,7 +769,7 @@ typedef void Tk_SegCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); typedef struct Tk_SegType { - char *name; /* Name of this kind of segment. */ + CONST char *name; /* Name of this kind of segment. */ int leftGravity; /* If a segment has zero size (e.g. a * mark or tag toggle), does it * attach to character to its left @@ -777,6 +801,23 @@ typedef struct Tk_SegType { * segment. */ } Tk_SegType; + +/* + * The following type and items describe different flags for text widget + * items to count. They are used in both tkText.c and tkTextIndex.c, + * in 'CountIndices', 'TkTextIndexBackChars', 'TkTextIndexForwChars', + * and 'TkTextIndexCount'. + */ + +typedef int TkTextCountType; + +#define COUNT_CHARS 0 +#define COUNT_INDICES 1 +#define COUNT_DISPLAY 2 +#define COUNT_IS_ELIDED 4 +#define COUNT_DISPLAY_CHARS (COUNT_CHARS | COUNT_DISPLAY) +#define COUNT_DISPLAY_INDICES (COUNT_INDICES | COUNT_DISPLAY) + /* * The constant below is used to specify a line when what is really * wanted is the entire text. For now, just use a very big number. @@ -791,6 +832,26 @@ typedef struct Tk_SegType { #define TK_POS_CHARS 30 +/* + * Mask used for those options which may impact the pixel height + * calculations of individual lines displayed in the widget. + */ +#define TK_TEXT_LINE_GEOMETRY 1 + +/* + * Used as 'action' values in calls to TkTextInvalidateLineMetrics + */ +#define TK_TEXT_INVALIDATE_ONLY 0 +#define TK_TEXT_INVALIDATE_INSERT 1 +#define TK_TEXT_INVALIDATE_DELETE 2 + +/* + * Used as special 'pickPlace' values in calls to TkTextSetYView. + * Zero or positive values indicate a number of pixels. + */ +#define TK_TEXT_PICKPLACE -1 +#define TK_TEXT_NOPIXELADJUST -2 + /* * Declarations for variables shared among the text-related files: */ @@ -808,6 +869,8 @@ EXTERN Tk_SegType tkTextToggleOffType; * but shouldn't be used anywhere else in Tk (or by Tk clients): */ +EXTERN int TkBTreeAdjustPixelHeight _ANSI_ARGS_((TkTextLine *linePtr, + int newPixelHeight)); EXTERN int TkBTreeCharTagged _ANSI_ARGS_((CONST TkTextIndex *indexPtr, TkTextTag *tagPtr)); EXTERN void TkBTreeCheck _ANSI_ARGS_((TkTextBTree tree)); @@ -819,16 +882,20 @@ EXTERN void TkBTreeDeleteChars _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr)); EXTERN TkTextLine * TkBTreeFindLine _ANSI_ARGS_((TkTextBTree tree, int line)); +EXTERN TkTextLine * TkBTreeFindPixelLine _ANSI_ARGS_((TkTextBTree tree, + int pixels, int *pixelOffset)); EXTERN TkTextTag ** TkBTreeGetTags _ANSI_ARGS_((CONST TkTextIndex *indexPtr, int *numTagsPtr)); EXTERN void TkBTreeInsertChars _ANSI_ARGS_((TkTextIndex *indexPtr, CONST char *string)); EXTERN int TkBTreeLineIndex _ANSI_ARGS_((TkTextLine *linePtr)); +EXTERN int TkBTreePixels _ANSI_ARGS_((TkTextLine *linePtr)); EXTERN void TkBTreeLinkSegment _ANSI_ARGS_((TkTextSegment *segPtr, TkTextIndex *indexPtr)); EXTERN TkTextLine * TkBTreeNextLine _ANSI_ARGS_((TkTextLine *linePtr)); EXTERN int TkBTreeNextTag _ANSI_ARGS_((TkTextSearch *searchPtr)); EXTERN int TkBTreeNumLines _ANSI_ARGS_((TkTextBTree tree)); +EXTERN int TkBTreeNumPixels _ANSI_ARGS_((TkTextBTree tree)); EXTERN TkTextLine * TkBTreePreviousLine _ANSI_ARGS_((TkTextLine *linePtr)); EXTERN int TkBTreePrevTag _ANSI_ARGS_((TkTextSearch *searchPtr)); EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr, @@ -837,7 +904,7 @@ EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr, EXTERN void TkBTreeStartSearchBack _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, TkTextSearch *searchPtr)); -EXTERN void TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr, +EXTERN int TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, int add)); EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree, @@ -845,10 +912,11 @@ EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree, EXTERN void TkTextBindProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); EXTERN void TkTextChanged _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *index1Ptr, TkTextIndex *index2Ptr)); + CONST TkTextIndex *index1Ptr, + CONST TkTextIndex *index2Ptr)); EXTERN int TkTextCharBbox _ANSI_ARGS_((TkText *textPtr, CONST TkTextIndex *indexPtr, int *xPtr, int *yPtr, - int *widthPtr, int *heightPtr)); + int *widthPtr, int *heightPtr, int *charWidthPtr)); EXTERN int TkTextCharLayoutProc _ANSI_ARGS_((TkText *textPtr, TkTextIndex *indexPtr, TkTextSegment *segPtr, int offset, int maxX, int maxChars, int noBreakYet, @@ -857,6 +925,10 @@ EXTERN void TkTextCreateDInfo _ANSI_ARGS_((TkText *textPtr)); EXTERN int TkTextDLineInfo _ANSI_ARGS_((TkText *textPtr, CONST TkTextIndex *indexPtr, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr, int *basePtr)); +EXTERN void TkTextEmbWinDisplayProc _ANSI_ARGS_(( + TkTextDispChunk *chunkPtr, int x, int y, + int lineHeight, int baseline, Display *display, + Drawable dst, int screenY)); EXTERN TkTextTag * TkTextCreateTag _ANSI_ARGS_((TkText *textPtr, CONST char *tagName)); EXTERN void TkTextFreeDInfo _ANSI_ARGS_((TkText *textPtr)); @@ -872,21 +944,32 @@ EXTERN CONST TkTextIndex* TkTextGetIndexFromObj _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *objPtr)); EXTERN TkTextTabArray * TkTextGetTabs _ANSI_ARGS_((Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *stringPtr)); +EXTERN void TkTextFindDisplayLineEnd _ANSI_ARGS_(( + TkText *textPtr, TkTextIndex *indexPtr, + int end, int *xOffset)); EXTERN void TkTextIndexBackBytes _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexBackChars _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, - TkTextIndex *dstPtr)); + TkTextIndex *dstPtr, TkTextCountType type)); EXTERN int TkTextIndexCmp _ANSI_ARGS_(( CONST TkTextIndex *index1Ptr, CONST TkTextIndex *index2Ptr)); -EXTERN void TkTextIndexForwBytes _ANSI_ARGS_(( +EXTERN int TkTextIndexCount _ANSI_ARGS_((CONST TkText *textPtr, + CONST TkTextIndex *index1Ptr, + CONST TkTextIndex *index2Ptr, + TkTextCountType type)); +EXTERN int TkTextIndexForwBytes _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexForwChars _ANSI_ARGS_(( CONST TkTextIndex *srcPtr, int count, - TkTextIndex *dstPtr)); + TkTextIndex *dstPtr, TkTextCountType type)); +EXTERN void TkTextIndexOfX _ANSI_ARGS_((TkText *textPtr, + int x, TkTextIndex *indexPtr)); +EXTERN int TkTextIndexYPixels _ANSI_ARGS_((TkText *textPtr, + CONST TkTextIndex *indexPtr)); EXTERN TkTextSegment * TkTextIndexToSeg _ANSI_ARGS_(( CONST TkTextIndex *indexPtr, int *offsetPtr)); EXTERN void TkTextInsertDisplayProc _ANSI_ARGS_(( @@ -898,11 +981,25 @@ EXTERN void TkTextLostSelection _ANSI_ARGS_(( EXTERN TkTextIndex * TkTextMakeCharIndex _ANSI_ARGS_((TkTextBTree tree, int lineIndex, int charIndex, TkTextIndex *indexPtr)); -EXTERN int TkTextIsElided _ANSI_ARGS_((TkText *textPtr, +EXTERN int TkTextMeasureDown _ANSI_ARGS_((TkText *textPtr, + TkTextIndex *srcPtr, int distance)); +EXTERN int TkTextIsElided _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *indexPtr)); EXTERN TkTextIndex * TkTextMakeByteIndex _ANSI_ARGS_((TkTextBTree tree, int lineIndex, int byteIndex, TkTextIndex *indexPtr)); +EXTERN int TkTextMakePixelIndex _ANSI_ARGS_((TkText *textPtr, + int pixelIndex, TkTextIndex *indexPtr)); +EXTERN void TkTextInvalidateLineMetrics _ANSI_ARGS_((TkText *textPtr, + TkTextLine *linePtr, int lineCount, int action)); +EXTERN void TkTextAsyncUpdateLineMetrics _ANSI_ARGS_((ClientData + clientData)); +EXTERN int TkTextUpdateLineMetrics _ANSI_ARGS_((TkText *textPtr, + int lineNum, int endLine, int doThisMuch)); +EXTERN int TkTextUpdateOneLine _ANSI_ARGS_((TkText *textPtr, + TkTextLine *linePtr)); +EXTERN void TkTextUpdateYScrollbar _ANSI_ARGS_((ClientData + clientData)); EXTERN int TkTextMarkCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextMarkNameToIndex _ANSI_ARGS_((TkText *textPtr, @@ -923,7 +1020,8 @@ EXTERN void TkTextRedrawRegion _ANSI_ARGS_((TkText *textPtr, EXTERN void TkTextRedrawTag _ANSI_ARGS_((TkText *textPtr, TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, int withTag)); -EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr)); +EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr, + int mask)); EXTERN int TkTextScanCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextSeeCmd _ANSI_ARGS_((TkText *textPtr, @@ -949,9 +1047,6 @@ EXTERN int TkTextXviewCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); EXTERN int TkTextYviewCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); -/* Use a helper from tkCanvas.c */ -EXTERN CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc, - Tcl_Obj *CONST *objv)); # undef TCL_STORAGE_CLASS # define TCL_STORAGE_CLASS DLLIMPORT diff --git a/generic/tkTextBTree.c b/generic/tkTextBTree.c index 762d5f3..63d10c0 100644 --- a/generic/tkTextBTree.c +++ b/generic/tkTextBTree.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextBTree.c,v 1.7 2003/05/19 13:04:23 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextBTree.c,v 1.8 2003/10/31 09:02:10 vincentdarley Exp $ */ #include "tkInt.h" @@ -55,6 +55,7 @@ typedef struct Node { int numChildren; /* Number of children of this node. */ int numLines; /* Total number of lines (leaves) in * the subtree rooted here. */ + int numPixels; } Node; /* @@ -217,7 +218,7 @@ TkBTreeCreate(textPtr) register Node *rootPtr; register TkTextLine *linePtr, *linePtr2; register TkTextSegment *segPtr; - + /* * The tree will initially have two empty lines. The second line * isn't actually part of the tree's contents, but its presence @@ -235,7 +236,13 @@ TkBTreeCreate(textPtr) rootPtr->children.linePtr = linePtr; rootPtr->numChildren = 2; rootPtr->numLines = 2; - + rootPtr->numPixels = textPtr->charHeight; + linePtr->pixelHeight = textPtr->charHeight; + linePtr->pixelCalculationEpoch = 0; + /* The last line permanently has a pixel height of zero */ + linePtr2->pixelHeight = 0; + linePtr2->pixelCalculationEpoch = 1; + linePtr->parentPtr = rootPtr; linePtr->nextPtr = linePtr2; segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1)); @@ -245,7 +252,7 @@ TkBTreeCreate(textPtr) segPtr->size = 1; segPtr->body.chars[0] = '\n'; segPtr->body.chars[1] = 0; - + linePtr2->parentPtr = rootPtr; linePtr2->nextPtr = NULL; segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1)); @@ -371,6 +378,54 @@ DeleteSummaries(summaryPtr) /* *---------------------------------------------------------------------- * + * TkBTreeAdjustPixelHeight -- + * + * Adjust the pixel height of a given logical line to the + * specified value. + * + * Results: + * Total number of valid pixels currently known in the tree. + * + * Side effects: + * Updates overall data structures so pixel height count is + * consistent. + * + *---------------------------------------------------------------------- + */ + +int +TkBTreeAdjustPixelHeight(linePtr, newPixelHeight) + register TkTextLine *linePtr; /* The logical line to update */ + int newPixelHeight; /* The line's known height + * in pixels */ +{ + register Node *nodePtr; + int changeToPixelCount; /* Counts change to total number of + * pixels in file. */ + + changeToPixelCount = newPixelHeight - linePtr->pixelHeight; + + /* + * Increment the pixel counts in all the parent nodes of the + * current line, then rebalance the tree if necessary. + */ + + nodePtr = linePtr->parentPtr; + nodePtr->numPixels += changeToPixelCount; + + while (nodePtr->parentPtr != NULL) { + nodePtr = nodePtr->parentPtr; + nodePtr->numPixels += changeToPixelCount; + } + + linePtr->pixelHeight = newPixelHeight; + + return nodePtr->numPixels; +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeInsertChars -- * * Insert characters at a given position in a B-tree. @@ -414,6 +469,8 @@ TkBTreeInsertChars(indexPtr, string) * one in current chunk. */ int changeToLineCount; /* Counts change to total number of * lines in file. */ + int changeToPixelCount; /* Counts change to total number of + * pixels in file. */ prevPtr = SplitSeg(indexPtr); linePtr = indexPtr->linePtr; @@ -426,6 +483,7 @@ TkBTreeInsertChars(indexPtr, string) */ changeToLineCount = 0; + changeToPixelCount = 0; while (*string != 0) { for (eol = string; *eol != 0; eol++) { if (*eol == '\n') { @@ -461,11 +519,19 @@ TkBTreeInsertChars(indexPtr, string) newLinePtr->nextPtr = linePtr->nextPtr; linePtr->nextPtr = newLinePtr; newLinePtr->segPtr = segPtr->nextPtr; + /* + * Set up a starting default height, which will be re-adjusted + * later + */ + newLinePtr->pixelHeight = linePtr->pixelHeight; + newLinePtr->pixelCalculationEpoch = 0; + segPtr->nextPtr = NULL; linePtr = newLinePtr; curPtr = NULL; changeToLineCount++; - + changeToPixelCount += newLinePtr->pixelHeight; + string = eol; } @@ -478,15 +544,26 @@ TkBTreeInsertChars(indexPtr, string) if (linePtr != indexPtr->linePtr) { CleanupLine(linePtr); } + + /* + * I don't believe it's possible for either of the two lines + * passed to this function to be the last line of text, but + * the function is robust to that case anyway. (We must never + * re-calculated the line height of the last line). + */ + TkTextInvalidateLineMetrics(((BTree*)indexPtr->tree)->textPtr, + indexPtr->linePtr, changeToLineCount, + TK_TEXT_INVALIDATE_INSERT); /* - * Increment the line counts in all the parent nodes of the insertion - * point, then rebalance the tree if necessary. + * Increment the line and pixel counts in all the parent nodes of the + * insertion point, then rebalance the tree if necessary. */ for (nodePtr = linePtr->parentPtr ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines += changeToLineCount; + nodePtr->numPixels += changeToPixelCount; } nodePtr = linePtr->parentPtr; nodePtr->numChildren += changeToLineCount; @@ -649,7 +726,8 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) TkTextSegment *segPtr, *nextPtr; TkTextLine *curLinePtr; Node *curNodePtr, *nodePtr; - + int changeToLineCount = 0; + /* * Tricky point: split at index2Ptr first; otherwise the split * at index2Ptr may invalidate segPtr and/or prevPtr. @@ -675,6 +753,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) */ curLinePtr = index1Ptr->linePtr; + curNodePtr = curLinePtr->parentPtr; while (segPtr != lastPtr) { if (segPtr == NULL) { @@ -696,7 +775,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; + nodePtr->numPixels -= curLinePtr->pixelHeight; } + changeToLineCount++; curNodePtr->numChildren--; ckfree((char *) curLinePtr); } @@ -768,7 +849,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; + nodePtr->numPixels -= index2Ptr->linePtr->pixelHeight; } + changeToLineCount++; curNodePtr->numChildren--; prevLinePtr = curNodePtr->children.linePtr; if (prevLinePtr == index2Ptr->linePtr) { @@ -780,6 +863,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr; } ckfree((char *) index2Ptr->linePtr); + Rebalance((BTree *) index2Ptr->tree, curNodePtr); } @@ -790,6 +874,18 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) CleanupLine(index1Ptr->linePtr); /* + * This line now needs to have its height recalculated. For safety, + * ensure we don't call this function with the last artificial line + * of text. I _believe_ that it isn't possible to get this far with + * the last line, but it is good to be safe. + */ + if (TkBTreeNextLine(index1Ptr->linePtr) != NULL) { + TkTextInvalidateLineMetrics(((BTree*)index1Ptr->tree)->textPtr, + index1Ptr->linePtr, changeToLineCount, + TK_TEXT_INVALIDATE_DELETE); + } + + /* * Lastly, rebalance the first node of the range. */ @@ -865,6 +961,82 @@ TkBTreeFindLine(tree, line) /* *---------------------------------------------------------------------- * + * TkBTreeFindPixelLine -- + * + * Find a particular line in a B-tree based on its pixel count. + * + * Results: + * The return value is a pointer to the line structure for the + * line which contains the pixel "pixels", or NULL if no such + * line exists. If the first line is of height 20, then pixels + * 0-19 will return it, and pixels = 20 will return the next + * line. + * + * If pixelOffset is non-NULL, it is set to the amount by which + * 'pixels' exceeds the first pixel located on the returned + * line. This should always be non-negative. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TkTextLine * +TkBTreeFindPixelLine(tree, pixels, pixelOffset) + TkTextBTree tree; /* B-tree in which to find line. */ + int pixels; /* Index of desired line. */ + int *pixelOffset; /* Used to return offset */ +{ + BTree *treePtr = (BTree *) tree; + register Node *nodePtr; + register TkTextLine *linePtr; + int pixelsLeft; + + nodePtr = treePtr->rootPtr; + pixelsLeft = pixels; + + if ((pixels < 0) || (pixels > nodePtr->numPixels)) { + return NULL; + } + + /* + * Work down through levels of the tree until a node is found at + * level 0. + */ + + while (nodePtr->level != 0) { + for (nodePtr = nodePtr->children.nodePtr; + nodePtr->numPixels <= pixelsLeft; + nodePtr = nodePtr->nextPtr) { + if (nodePtr == NULL) { + panic("TkBTreeFindLine ran out of nodes"); + } + pixelsLeft -= nodePtr->numPixels; + } + } + + /* + * Work through the lines attached to the level-0 node. + */ + + for (linePtr = nodePtr->children.linePtr; + linePtr->pixelHeight < pixelsLeft; + linePtr = linePtr->nextPtr) { + if (linePtr == NULL) { + panic("TkBTreeFindLine ran out of lines"); + } + pixelsLeft -= linePtr->pixelHeight; + } + if (pixelOffset != NULL && linePtr != NULL) { + *pixelOffset = pixelsLeft; + } + return linePtr; +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeNextLine -- * * Given an existing line in a B-tree, this procedure locates the @@ -988,6 +1160,72 @@ TkBTreePreviousLine(linePtr) /* *---------------------------------------------------------------------- * + * TkBTreePixels -- + * + * Given a pointer to a line in a B-tree, return the numerical + * pixel index of the top of that line (i.e. the result does + * not include the height of the given line). + * + * Since the last line of text (the artificial one) has zero + * height by defintion, calling this with the last line will + * return the total number of pixels in the widget. + * + * Results: + * The result is the index of linePtr within the tree, where 0 + * corresponds to the first line in the tree. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkBTreePixels(linePtr) + TkTextLine *linePtr; /* Pointer to existing line in + * B-tree. */ +{ + register TkTextLine *linePtr2; + register Node *nodePtr, *parentPtr, *nodePtr2; + int index; + + /* + * First count how many lines precede this one in its level-0 + * node. + */ + + nodePtr = linePtr->parentPtr; + index = 0; + for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr; + linePtr2 = linePtr2->nextPtr) { + if (linePtr2 == NULL) { + panic("TkBTreePixels couldn't find line"); + } + index += linePtr2->pixelHeight; + } + + /* + * Now work up through the levels of the tree one at a time, + * counting how many lines are in nodes preceding the current + * node. + */ + + for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL; + nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) { + for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr; + nodePtr2 = nodePtr2->nextPtr) { + if (nodePtr2 == NULL) { + panic("TkBTreePixels couldn't find node"); + } + index += nodePtr2->numPixels; + } + } + return index; +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeLineIndex -- * * Given a pointer to a line in a B-tree, return the numerical @@ -1137,7 +1375,9 @@ TkBTreeUnlinkSegment(tree, segPtr, linePtr) * a B-tree of text. * * Results: - * None. + * 1 if the tags on any characters in the range were changed, + * and zero otherwise (i.e. if the tag was already absent (add = 0) + * or present (add = 1) on the index range in question). * * Side effects: * The given tag is added to the given range of characters @@ -1150,7 +1390,7 @@ TkBTreeUnlinkSegment(tree, segPtr, linePtr) *---------------------------------------------------------------------- */ -void +int TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) register TkTextIndex *index1Ptr; /* Indicates first character in * range. */ @@ -1166,7 +1406,8 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) TkTextLine *cleanupLinePtr; int oldState; int changed; - + int anyChanges = 0; + /* * See whether the tag is present at the start of the range. If * the state doesn't already match what we want then add a toggle @@ -1188,6 +1429,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) segPtr->size = 0; segPtr->body.toggle.tagPtr = tagPtr; segPtr->body.toggle.inNodeCounts = 0; + anyChanges = 1; } /* @@ -1199,6 +1441,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search); cleanupLinePtr = index1Ptr->linePtr; while (TkBTreeNextTag(&search)) { + anyChanges = 1; oldState ^= 1; segPtr = search.segPtr; prevPtr = search.curIndex.linePtr->segPtr; @@ -1257,6 +1500,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) segPtr->size = 0; segPtr->body.toggle.tagPtr = tagPtr; segPtr->body.toggle.inNodeCounts = 0; + anyChanges = 1; } /* @@ -1264,14 +1508,17 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add) * these are different. */ - CleanupLine(cleanupLinePtr); - if (cleanupLinePtr != index2Ptr->linePtr) { - CleanupLine(index2Ptr->linePtr); + if (anyChanges) { + CleanupLine(cleanupLinePtr); + if (cleanupLinePtr != index2Ptr->linePtr) { + CleanupLine(index2Ptr->linePtr); + } } if (tkBTreeDebug) { TkBTreeCheck(index1Ptr->tree); } + return anyChanges; } /* @@ -1789,7 +2036,8 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) searchPtr->curIndex = index0; index1Ptr = &index0; } else { - TkTextIndexBackChars(index1Ptr, 1, &searchPtr->curIndex); + TkTextIndexBackChars(index1Ptr, 1, &searchPtr->curIndex, + COUNT_INDICES); } searchPtr->segPtr = NULL; searchPtr->nextPtr = TkTextIndexToSeg(&searchPtr->curIndex, &offset); @@ -1805,7 +2053,7 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) backOne = *index2Ptr; searchPtr->lastPtr = NULL; /* Signals special case for 1.0 */ } else { - TkTextIndexBackChars(index2Ptr, 1, &backOne); + TkTextIndexBackChars(index2Ptr, 1, &backOne, COUNT_INDICES); searchPtr->lastPtr = TkTextIndexToSeg(&backOne, (int *) NULL); } searchPtr->tagPtr = tagPtr; @@ -2451,7 +2699,7 @@ TkBTreeGetTags(indexPtr, numTagsPtr) /* ARGSUSED */ int TkTextIsElided(textPtr, indexPtr) - TkText *textPtr; /* Overall information about text widget. */ + CONST TkText *textPtr; /* Overall information about text widget. */ CONST TkTextIndex *indexPtr;/* The character in the text for which * display information is wanted. */ { @@ -2469,7 +2717,7 @@ TkTextIsElided(textPtr, indexPtr) register TkTextTag *tagPtr = NULL; register int i, index; - /* almost always avoid malloc, so stay out of system calls */ + /* Almost always avoid malloc, so stay out of system calls */ if (LOTSA_TAGS < numTags) { tagCnts = (int *)ckalloc((unsigned)sizeof(int) * numTags); tagPtrs = (TkTextTag **)ckalloc((unsigned)sizeof(TkTextTag *) * numTags); @@ -2806,7 +3054,7 @@ CheckNodeConsistency(nodePtr) register Summary *summaryPtr, *summaryPtr2; register TkTextLine *linePtr; register TkTextSegment *segPtr; - int numChildren, numLines, toggleCount, minChildren; + int numChildren, numLines, numPixels, toggleCount, minChildren; if (nodePtr->parentPtr != NULL) { minChildren = MIN_CHILDREN; @@ -2823,6 +3071,7 @@ CheckNodeConsistency(nodePtr) numChildren = 0; numLines = 0; + numPixels = 0; if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { @@ -2850,6 +3099,7 @@ CheckNodeConsistency(nodePtr) } numChildren++; numLines++; + numPixels += linePtr->pixelHeight; } } else { for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL; @@ -2881,6 +3131,7 @@ CheckNodeConsistency(nodePtr) } numChildren++; numLines += childNodePtr->numLines; + numPixels += childNodePtr->numPixels; } } if (numChildren != nodePtr->numChildren) { @@ -2891,6 +3142,10 @@ CheckNodeConsistency(nodePtr) panic("CheckNodeConsistency: mismatch in numLines (%d %d)", numLines, nodePtr->numLines); } + if (numPixels != nodePtr->numPixels) { + panic("CheckNodeConsistency: mismatch in numPixels (%d %d)", + numPixels, nodePtr->numPixels); + } for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { @@ -2997,6 +3252,7 @@ Rebalance(treePtr, nodePtr) newPtr->children.nodePtr = nodePtr; newPtr->numChildren = 1; newPtr->numLines = nodePtr->numLines; + newPtr->numPixels = nodePtr->numPixels; RecomputeNodeCounts(newPtr); treePtr->rootPtr = newPtr; } @@ -3209,6 +3465,7 @@ RecomputeNodeCounts(nodePtr) } nodePtr->numChildren = 0; nodePtr->numLines = 0; + nodePtr->numPixels = 0; /* * Scan through the children, adding the childrens' tag counts into @@ -3221,6 +3478,7 @@ RecomputeNodeCounts(nodePtr) linePtr = linePtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines++; + nodePtr->numPixels += linePtr->pixelHeight; linePtr->parentPtr = nodePtr; for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { @@ -3252,6 +3510,7 @@ RecomputeNodeCounts(nodePtr) childPtr = childPtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines += childPtr->numLines; + nodePtr->numPixels += childPtr->numPixels; childPtr->parentPtr = nodePtr; for (summaryPtr2 = childPtr->summaryPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { @@ -3343,6 +3602,34 @@ TkBTreeNumLines(tree) } /* + *---------------------------------------------------------------------- + * + * TkBTreeNumPixels -- + * + * This procedure returns a count of the number of pixels of + * text present in a given B-tree. + * + * Results: + * The return value is a count of the number of usable pixels in + * tree (since the dummy line used to mark the end of the tree is + * maintained with zero height, it doesn't feature in this + * calculation). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TkBTreeNumPixels(tree) + TkTextBTree tree; /* Information about tree. */ +{ + BTree *treePtr = (BTree *) tree; + return treePtr->rootPtr->numPixels; +} + +/* *-------------------------------------------------------------- * * CharSplitProc -- diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index 48f2963..701fe96 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -3,7 +3,9 @@ * * This module provides facilities to display text widgets. It is * the only place where information is kept about the screen layout - * of text widgets. + * of text widgets. (Well, strictly, each TkTextLine caches its + * last observed pixel height, but that information is + * calculated here). * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -11,7 +13,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextDisp.c,v 1.17 2003/07/07 20:39:50 hobbs Exp $ + * RCS: @(#) $Id: tkTextDisp.c,v 1.18 2003/10/31 09:02:10 vincentdarley Exp $ */ #include "tkPort.h" @@ -119,11 +121,15 @@ typedef struct DLine { int y; /* Y-position at which line is supposed to * be drawn (topmost pixel of rectangular * area occupied by line). */ - int oldY; /* Y-position at which line currently - * appears on display. -1 means line isn't - * currently visible on display and must be - * redrawn. This is used to move lines by - * scrolling rather than re-drawing. */ + int oldY; /* Y-position at which line + * currently appears on display. This + * is used to move lines by scrolling + * rather than re-drawing. If 'flags' + * have the OLD_Y_INVALID bit set, then + * we will never examine this field + * (which means line isn't currently + * visible on display and must be + * redrawn). */ int height; /* Height of line, in pixels. */ int baseline; /* Offset of text baseline from y, in * pixels. */ @@ -171,14 +177,20 @@ typedef struct DLine { * BOTTOM_LINE - Non-zero means that this was the bottom line * in the window the last time that the window * was laid out. - * IS_DISABLED - This Dline cannot be edited. + * OLD_Y_INVALID - The value of oldY in the structure is + * not valid or useful and should not be + * examined. 'oldY' is only useful when the + * DLine is currently displayed at a + * different position and we wish to + * re-display it via scrolling, so this + * means the DLine needs redrawing. */ #define HAS_3D_BORDER 1 #define NEW_LAYOUT 2 #define TOP_LINE 4 #define BOTTOM_LINE 8 -#define IS_DISABLED 16 +#define OLD_Y_INVALID 16 /* * Overall display information for a text widget: @@ -189,6 +201,10 @@ typedef struct TextDInfo { * to TextStyles for this widget. */ DLine *dLinePtr; /* First in list of all display lines for * this widget, in order from top to bottom. */ + int topPixelOffset; /* Identifies first pixel in top display + * line to display in window. */ + int newTopPixelOffset; /* Desired first pixel in top display + * line to display in window. */ GC copyGC; /* Graphics context for copying from off- * screen pixmaps onto screen. */ GC scrollGC; /* Graphics context for copying from one place @@ -216,11 +232,11 @@ typedef struct TextDInfo { * Information used for scrolling: */ - int newByteOffset; /* Desired x scroll position, measured as the + int newXByteOffset; /* Desired x scroll position, measured as the * number of average-size characters off-screen * to the left for a line with no left * margin. */ - int curPixelOffset; /* Actual x scroll position, measured as the + int curXPixelOffset; /* Actual x scroll position, measured as the * number of pixels off-screen to the left. */ int maxLength; /* Length in pixels of longest line that's * visible in window (length may exceed window @@ -243,7 +259,7 @@ typedef struct TextDInfo { * left edge of the window when the scan * started. */ int scanMarkX; /* X-position of mouse at time scan started. */ - int scanTotalScroll; /* Total scrolling (in screen lines) that has + int scanTotalYScroll; /* Total scrolling (in screen pixels) that has * occurred since scanMarkY was set. */ int scanMarkY; /* Y-position of mouse at time scan started. */ @@ -264,6 +280,28 @@ typedef struct TextDInfo { * could dump core. */ int flags; /* Various flag values: see below for * definitions. */ + /* + * Information used to handle the asynchronous updating of the + * y-scrollbar and the vertical height calculations: + */ + + int lineMetricUpdateEpoch; /* Stores a number which is incremented + * each time the text widget changes in + * a significant way (e.g. resizing or + * geometry-influencing tag changes). */ + int currentMetricUpdateLine;/* Stores a counter which is used to + * iterate over the logical lines + * contained in the widget and update + * their geometry calculations, if they + * are out of date. */ + int lastMetricUpdateLine; /* When the current update line reaches + * this line, we are done and should + * stop the asychronous callback + * mechanism. */ + Tcl_TimerToken lineUpdateTimer; /* A token pointing to the current + * line metric update callback. */ + Tcl_TimerToken scrollbarTimer; /* A token pointing to the current + * scrollbar update callback. */ } TextDInfo; /* @@ -302,6 +340,20 @@ typedef struct CharInfo { #define REPICK_NEEDED 8 /* + * Action values for FreeDLines: + * + * DLINE_FREE: Free the lines, but no need to unlink them + * from the current list of actual display lines. + * DLINE_UNLINK: Free and unlink from current display. + * DLINE_FREE_TEMP: Free, but don't unlink, and also don't + * set 'dLinesInvalidated'. + */ + +#define DLINE_FREE 0 +#define DLINE_UNLINK 1 +#define DLINE_FREE_TEMP 2 + +/* * The following counters keep statistics about redisplay that can be * checked to see how clever this code is at reducing redisplays. */ @@ -352,33 +404,58 @@ static void DisplayText _ANSI_ARGS_((ClientData clientData)); static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr, CONST TkTextIndex *indexPtr)); static void FreeDLines _ANSI_ARGS_((TkText *textPtr, - DLine *firstPtr, DLine *lastPtr, int unlink)); + DLine *firstPtr, DLine *lastPtr, int action)); static void FreeStyle _ANSI_ARGS_((TkText *textPtr, TextStyle *stylePtr)); static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr)); + CONST TkTextIndex *indexPtr)); static void GetXView _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int report)); static void GetYView _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, int report)); +static int GetPixelCount _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr)); static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr)); + CONST TkTextIndex *indexPtr)); static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont, CONST char *source, int maxBytes, int startX, int maxX, int tabOrigin, int *nextXPtr)); static void MeasureUp _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *srcPtr, int distance, - TkTextIndex *dstPtr)); + CONST TkTextIndex *srcPtr, int distance, + TkTextIndex *dstPtr, int *overlap)); static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x, int tabOrigin)); static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr)); -static void ScrollByLines _ANSI_ARGS_((TkText *textPtr, +static void YScrollByLines _ANSI_ARGS_((TkText *textPtr, + int offset)); +static void YScrollByPixels _ANSI_ARGS_((TkText *textPtr, int offset)); static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, TkTextTabArray *tabArrayPtr, int index, int x, int maxX)); static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, TkRegion region)); +static int TextCalculateDisplayLineHeight _ANSI_ARGS_(( + TkText *textPtr, CONST TkTextIndex *indexPtr, + int *byteCountPtr)); +static void DlineIndexOfX _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr, int x, TkTextIndex *indexPtr)); +static int DlineXOfIndex _ANSI_ARGS_((TkText *textPtr, + DLine *dlPtr, int byteIndex)); +static int TextGetScrollInfoObj _ANSI_ARGS_((Tcl_Interp *interp, + TkText *textPtr, int objc, + Tcl_Obj *CONST objv[], double *dblPtr, + int *intPtr)); + +/* + * Result values returned by TextGetScrollInfo: + */ + +#define TKTEXT_SCROLL_MOVETO 1 +#define TKTEXT_SCROLL_PAGES 2 +#define TKTEXT_SCROLL_UNITS 3 +#define TKTEXT_SCROLL_ERROR 4 +#define TKTEXT_SCROLL_PIXELS 5 /* @@ -412,10 +489,10 @@ TkTextCreateDInfo(textPtr) dInfoPtr->copyGC = None; gcValues.graphics_exposures = True; dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, - &gcValues); + &gcValues); dInfoPtr->topOfEof = 0; - dInfoPtr->newByteOffset = 0; - dInfoPtr->curPixelOffset = 0; + dInfoPtr->newXByteOffset = 0; + dInfoPtr->curXPixelOffset = 0; dInfoPtr->maxLength = 0; dInfoPtr->xScrollFirst = -1; dInfoPtr->xScrollLast = -1; @@ -423,10 +500,25 @@ TkTextCreateDInfo(textPtr) dInfoPtr->yScrollLast = -1; dInfoPtr->scanMarkIndex = 0; dInfoPtr->scanMarkX = 0; - dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanTotalYScroll = 0; dInfoPtr->scanMarkY = 0; dInfoPtr->dLinesInvalidated = 0; dInfoPtr->flags = DINFO_OUT_OF_DATE; + dInfoPtr->topPixelOffset = 0; + dInfoPtr->newTopPixelOffset = 0; + dInfoPtr->currentMetricUpdateLine = -1; + dInfoPtr->lastMetricUpdateLine = -1; + dInfoPtr->lineMetricUpdateEpoch = 1; + + /* Add a refCount for each of the idle call-backs */ + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); + textPtr->refCount++; + dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, + TkTextUpdateYScrollbar, (ClientData) textPtr); + + textPtr->dInfoPtr = dInfoPtr; } @@ -460,7 +552,7 @@ TkTextFreeDInfo(textPtr) * all free then styleTable will be empty. */ - FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); + FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK); Tcl_DeleteHashTable(&dInfoPtr->styleTable); if (dInfoPtr->copyGC != None) { Tk_FreeGC(textPtr->display, dInfoPtr->copyGC); @@ -469,6 +561,16 @@ TkTextFreeDInfo(textPtr) if (dInfoPtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr); } + if (dInfoPtr->lineUpdateTimer != NULL) { + Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer); + textPtr->refCount--; + dInfoPtr->lineUpdateTimer = NULL; + } + if (dInfoPtr->scrollbarTimer != NULL) { + Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer); + textPtr->refCount--; + dInfoPtr->scrollbarTimer = NULL; + } ckfree((char *) dInfoPtr); } @@ -493,7 +595,7 @@ TkTextFreeDInfo(textPtr) static TextStyle * GetStyle(textPtr, indexPtr) TkText *textPtr; /* Overall information about text widget. */ - TkTextIndex *indexPtr; /* The character in the text for which + CONST TkTextIndex *indexPtr;/* The character in the text for which * display information is wanted. */ { TkTextTag **tagPtrs; @@ -762,6 +864,22 @@ FreeStyle(textPtr, stylePtr) * * Side effects: * Storage is allocated for the new DLine. + * + * See the comments in 'GetYView' for some thoughts on what the + * side-effects of this call (or its callers) should be -- the + * synchronisation of TkTextLine->pixelHeight with the sum of + * the results of this function operating on all display lines + * within each logical line. Ideally the code should be + * refactored to ensure the cached pixel height is never behind + * what is known when this function is called elsewhere. + * + * Unfortunately, this function is currently called from many + * different places, not just to layout a display line for actual + * display, but also simply to calculate some metric or other of one + * or more display lines (typically the height). It would be a good + * idea to do some profiling of typical text widget usage and the + * way in which this is called and see if some optimization could or + * should be done. * *---------------------------------------------------------------------- */ @@ -769,7 +887,7 @@ FreeStyle(textPtr, stylePtr) static DLine * LayoutDLine(textPtr, indexPtr) TkText *textPtr; /* Overall information about text widget. */ - TkTextIndex *indexPtr; /* Beginning of display line. May not + CONST TkTextIndex *indexPtr;/* Beginning of display line. May not * necessarily point to a character segment. */ { register DLine *dlPtr; /* New display line. */ @@ -825,12 +943,12 @@ LayoutDLine(textPtr, indexPtr) dlPtr->index = *indexPtr; dlPtr->byteCount = 0; dlPtr->y = 0; - dlPtr->oldY = -1; + dlPtr->oldY = 0; /* Only setting this to avoid compiler warnings */ dlPtr->height = 0; dlPtr->baseline = 0; dlPtr->chunkPtr = NULL; dlPtr->nextPtr = NULL; - dlPtr->flags = NEW_LAYOUT; + dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID; /* * Special case entirely elide line as there may be 1000s or more @@ -900,17 +1018,26 @@ LayoutDLine(textPtr, indexPtr) while (segPtr != NULL) { /* - * Every line still gets at least one chunk due to expectations - * in the rest of the code, but we are able to skip elided portions - * of the line quickly. - * If current chunk is elided and last chunk was too, coalese + * Every logical line still gets at least one chunk due to + * expectations in the rest of the code, but we are able to skip + * elided portions of the line quickly. + * + * If current chunk is elided and last chunk was too, coalese. + * + * This also means that each logical line which is entirely + * elided still gets laid out into a DLine, but with zero height. + * This isn't particularly a problem, but it does seem somewhat + * unnecessary. If/when we fix [Tk Bug 443848] (see below) + * then we will probably have to remove such zero height DLines + * too. */ if (elide && (lastChunkPtr != NULL) && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) { if ((elidesize = segPtr->size - byteOffset) > 0) { curIndex.byteIndex += elidesize; lastChunkPtr->numBytes += elidesize; - breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes; + breakByteOffset = lastChunkPtr->breakIndex + = lastChunkPtr->numBytes; /* * If have we have a tag toggle, there is a chance * that invisibility state changed, so bail out @@ -990,22 +1117,27 @@ LayoutDLine(textPtr, indexPtr) } chunkPtr->x = x; if (elide && maxBytes) { - /* don't free style here, as other code expects to be able to do that */ - /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; + /* + * Don't free style here, as other code expects to be able to + * do that. + */ + /* breakByteOffset =*/ + chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes; chunkPtr->width = 0; chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0; - /* would just like to point to canonical empty chunk */ + /* Would just like to point to canonical empty chunk */ chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL; chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL; chunkPtr->measureProc = ElideMeasureProc; chunkPtr->bboxProc = ElideBboxProc; code = 1; - } else - code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, + } else { + code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr, byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode, chunkPtr); + } if (code <= 0) { FreeStyle(textPtr, chunkPtr->stylePtr); if (code < 0) { @@ -1029,7 +1161,7 @@ LayoutDLine(textPtr, indexPtr) } break; } - if (chunkPtr->numBytes > 0) { + if (!elide && chunkPtr->numBytes > 0) { noCharsYet = 0; lastCharChunkPtr = chunkPtr; } @@ -1073,6 +1205,16 @@ LayoutDLine(textPtr, indexPtr) if (byteOffset >= segPtr->size) { byteOffset = 0; segPtr = segPtr->nextPtr; + if (elide && segPtr == NULL) { + /* + * An elided section started on this line, and carries on + * until the newline. Currently this forces a new line + * anyway (i.e. even though the newline is elided it + * still takes effect). This is because the code + * currently doesn't allow two or more logical lines to + * appear on the same display line. [Tk Bug #443848] + */ + } } chunkPtr = NULL; @@ -1240,7 +1382,7 @@ UpdateDisplayInfo(textPtr) register DLine *dlPtr, *prevPtr; TkTextIndex index; TkTextLine *lastLinePtr; - int y, maxY, pixelOffset, maxOffset; + int y, maxY, xPixelOffset, maxOffset, lineHeight; if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) { return; @@ -1254,7 +1396,12 @@ UpdateDisplayInfo(textPtr) index = textPtr->topIndex; dlPtr = FindDLine(dInfoPtr->dLinePtr, &index); if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) { - FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1); + FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK); + } + if (index.byteIndex == 0) { + lineHeight = 0; + } else { + lineHeight = -1; } /* @@ -1265,10 +1412,10 @@ UpdateDisplayInfo(textPtr) */ lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); + TkBTreeNumLines(textPtr->tree)); dlPtr = dInfoPtr->dLinePtr; prevPtr = NULL; - y = dInfoPtr->y; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; maxY = dInfoPtr->maxY; while (1) { register DLine *newPtr; @@ -1300,6 +1447,7 @@ UpdateDisplayInfo(textPtr) */ if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) { + /* * Case (b) -- must make new DLine. */ @@ -1325,7 +1473,7 @@ UpdateDisplayInfo(textPtr) } else { prevPtr->nextPtr = newPtr; if (prevPtr->flags & HAS_3D_BORDER) { - prevPtr->oldY = -1; + prevPtr->flags |= OLD_Y_INVALID; } } newPtr->nextPtr = dlPtr; @@ -1343,7 +1491,7 @@ UpdateDisplayInfo(textPtr) if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL) && (prevPtr->flags & (NEW_LAYOUT))) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } goto lineOK; } @@ -1357,7 +1505,7 @@ UpdateDisplayInfo(textPtr) */ newPtr = dlPtr->nextPtr; - FreeDLines(textPtr, dlPtr, newPtr, 0); + FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE); dlPtr = newPtr; if (prevPtr != NULL) { prevPtr->nextPtr = newPtr; @@ -1374,6 +1522,9 @@ UpdateDisplayInfo(textPtr) lineOK: dlPtr->y = y; y += dlPtr->height; + if (lineHeight != -1) { + lineHeight += dlPtr->height; + } TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); prevPtr = dlPtr; dlPtr = dlPtr->nextPtr; @@ -1392,10 +1543,36 @@ UpdateDisplayInfo(textPtr) nextPtr = nextPtr->nextPtr; } if (nextPtr != dlPtr) { - FreeDLines(textPtr, dlPtr, nextPtr, 0); + FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE); prevPtr->nextPtr = nextPtr; dlPtr = nextPtr; } + + if ((lineHeight != -1) + && (lineHeight > prevPtr->index.linePtr->pixelHeight)) { + /* + * The logical line height we just calculated is actually + * larger than the currently cached height of the + * text line. That is fine (the text line heights + * are only calculated asynchronously), but we must + * update the cached height so that any counts made + * with DLine pointers do not exceed counts made + * through the BTree. + */ + TkBTreeAdjustPixelHeight(prevPtr->index.linePtr, + lineHeight); + /* + * I believe we can be 100% sure that we started at the + * beginning of the logical line, so we can also adjust + * the 'pixelCalculationEpoch' to mark it as being up to + * date. There is a slight concern that we might not + * have got this right for the first line in the + * re-display. + */ + prevPtr->index.linePtr->pixelCalculationEpoch = + dInfoPtr->lineMetricUpdateEpoch; + } + lineHeight = 0; } /* @@ -1414,75 +1591,135 @@ UpdateDisplayInfo(textPtr) * Delete any DLine structures that don't fit on the screen. */ - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_UNLINK); /* *-------------------------------------------------------------- * If there is extra space at the bottom of the window (because * we've hit the end of the text), then bring in more lines at * the top of the window, if there are any, to fill in the view. + * + * Since the top line may only be partially visible, we try first + * to simply show more pixels from that line (newTopPixelOffset). + * If that isn't enough, we have to layout more lines. *-------------------------------------------------------------- */ if (y < maxY) { - int lineNum, spaceLeft, bytesToCount; - DLine *lowestPtr; - - /* - * Layout an entire text line (potentially > 1 display line), - * then link in as many display lines as fit without moving - * the bottom line out of the window. Repeat this until - * all the extra space has been used up or we've reached the - * beginning of the text. - */ - - spaceLeft = maxY - y; - lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); - bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; - if (bytesToCount == 0) { - bytesToCount = INT_MAX; - lineNum--; - } - for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { - index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); - index.byteIndex = 0; - lowestPtr = NULL; - - do { - dlPtr = LayoutDLine(textPtr, &index); - dlPtr->nextPtr = lowestPtr; - lowestPtr = dlPtr; - if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); - bytesToCount -= dlPtr->byteCount; - } while ((bytesToCount > 0) - && (index.linePtr == lowestPtr->index.linePtr)); + int spaceLeft = maxY - y; + + if (spaceLeft <= dInfoPtr->newTopPixelOffset) { + /* + * We can full up all the needed space just by showing + * more of the current top line + */ + dInfoPtr->newTopPixelOffset -= spaceLeft; + y += spaceLeft; + spaceLeft = 0; + } else { + int lineNum, bytesToCount; + DLine *lowestPtr; + + /* + * Add in all of the current top line, which won't + * be enough to bring y up to maxY (if it was we + * would be in the 'if' block above). + */ + y += dInfoPtr->newTopPixelOffset; + dInfoPtr->newTopPixelOffset = 0; /* - * Scan through the display lines from the bottom one up to - * the top one. + * Layout an entire text line (potentially > 1 display line), + * then link in as many display lines as fit without moving + * the bottom line out of the window. Repeat this until + * all the extra space has been used up or we've reached the + * beginning of the text. */ - while (lowestPtr != NULL) { - dlPtr = lowestPtr; - spaceLeft -= dlPtr->height; - if (spaceLeft < 0) { - break; + spaceLeft = maxY - y; + lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); + bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; + if (bytesToCount == 0) { + bytesToCount = INT_MAX; + lineNum--; + } + for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { + int pixelHeight = 0; + + index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + index.byteIndex = 0; + lowestPtr = NULL; + + do { + dlPtr = LayoutDLine(textPtr, &index); + pixelHeight += dlPtr->height; + dlPtr->nextPtr = lowestPtr; + lowestPtr = dlPtr; + if (dlPtr->length == 0 && dlPtr->height == 0) { + bytesToCount--; break; + } /* elide */ + TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + bytesToCount -= dlPtr->byteCount; + } while ((bytesToCount > 0) + && (index.linePtr == lowestPtr->index.linePtr)); + + /* + * We may not have examined the entire line (depending + * on the value of 'bytesToCount', so we only want + * to set this if it is genuinely bigger). + */ + if (pixelHeight > lowestPtr->index.linePtr->pixelHeight) { + TkBTreeAdjustPixelHeight(lowestPtr->index.linePtr, + pixelHeight); + if (index.linePtr != lowestPtr->index.linePtr) { + /* + * We examined the entire line, so can update + * the epoch. + */ + lowestPtr->index.linePtr->pixelCalculationEpoch = + dInfoPtr->lineMetricUpdateEpoch; + } } - lowestPtr = dlPtr->nextPtr; - dlPtr->nextPtr = dInfoPtr->dLinePtr; - dInfoPtr->dLinePtr = dlPtr; - if (tkTextDebug) { - char string[TK_POS_CHARS]; - TkTextPrintIndex(&dlPtr->index, string); - Tcl_SetVar2(textPtr->interp, "tk_textRelayout", + /* + * Scan through the display lines from the bottom one up to + * the top one. + */ + + while (lowestPtr != NULL) { + dlPtr = lowestPtr; + spaceLeft -= dlPtr->height; + lowestPtr = dlPtr->nextPtr; + dlPtr->nextPtr = dInfoPtr->dLinePtr; + dInfoPtr->dLinePtr = dlPtr; + if (tkTextDebug) { + char string[TK_POS_CHARS]; + + TkTextPrintIndex(&dlPtr->index, string); + Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); + } + if (spaceLeft <= 0) { + break; + } + } + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE); + bytesToCount = INT_MAX; + } + /* + * We've filled in the space we wanted to, and we + * need to store any extra overlap we've just + * created for the top line. + */ + if (lineNum >= 0) { + dInfoPtr->newTopPixelOffset = -spaceLeft; + if (spaceLeft > 0 + || dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) { + /* Bad situation */ + panic("Pixel height problem while laying out text widget"); } } - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - bytesToCount = INT_MAX; } /* @@ -1492,7 +1729,7 @@ UpdateDisplayInfo(textPtr) */ textPtr->topIndex = dInfoPtr->dLinePtr->index; - y = dInfoPtr->y; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { if (y > dInfoPtr->maxY) { @@ -1517,21 +1754,30 @@ UpdateDisplayInfo(textPtr) dlPtr = dInfoPtr->dLinePtr; if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } while (1) { if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; + } + /* + * If the old top-line was not completely showing (i.e. the + * pixelOffset is non-zero) and is no longer the top-line, then + * we must re-draw it. + */ + if ((dlPtr->flags & TOP_LINE) + && (dInfoPtr->topPixelOffset != 0) && (dlPtr != dInfoPtr->dLinePtr)) { + dlPtr->flags |= OLD_Y_INVALID; } if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } if (dlPtr->nextPtr == NULL) { if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & BOTTOM_LINE)) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } dlPtr->flags &= ~TOP_LINE; dlPtr->flags |= BOTTOM_LINE; @@ -1541,6 +1787,7 @@ UpdateDisplayInfo(textPtr) dlPtr = dlPtr->nextPtr; } dInfoPtr->dLinePtr->flags |= TOP_LINE; + dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset; /* * Arrange for scrollbars to be updated. @@ -1569,18 +1816,18 @@ UpdateDisplayInfo(textPtr) } maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x) + textPtr->charWidth - 1)/textPtr->charWidth; - if (dInfoPtr->newByteOffset > maxOffset) { - dInfoPtr->newByteOffset = maxOffset; + if (dInfoPtr->newXByteOffset > maxOffset) { + dInfoPtr->newXByteOffset = maxOffset; } - if (dInfoPtr->newByteOffset < 0) { - dInfoPtr->newByteOffset = 0; + if (dInfoPtr->newXByteOffset < 0) { + dInfoPtr->newXByteOffset = 0; } - pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth; - if (pixelOffset != dInfoPtr->curPixelOffset) { - dInfoPtr->curPixelOffset = pixelOffset; + xPixelOffset = dInfoPtr->newXByteOffset * textPtr->charWidth; + if (xPixelOffset != dInfoPtr->curXPixelOffset) { + dInfoPtr->curXPixelOffset = xPixelOffset; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - dlPtr->oldY = -1; + dlPtr->flags |= OLD_Y_INVALID; } } } @@ -1603,23 +1850,29 @@ UpdateDisplayInfo(textPtr) */ static void -FreeDLines(textPtr, firstPtr, lastPtr, unlink) - TkText *textPtr; /* Information about overall text - * widget. */ - register DLine *firstPtr; /* Pointer to first DLine to free up. */ - DLine *lastPtr; /* Pointer to DLine just after last - * one to free (NULL means everything - * starting with firstPtr). */ - int unlink; /* 1 means DLines are currently linked - * into the list rooted at - * textPtr->dInfoPtr->dLinePtr and - * they have to be unlinked. 0 means - * just free without unlinking. */ +FreeDLines(textPtr, firstPtr, lastPtr, action) + TkText *textPtr; /* Information about overall text + * widget. */ + register DLine *firstPtr; /* Pointer to first DLine to free up. */ + DLine *lastPtr; /* Pointer to DLine just after last + * one to free (NULL means everything + * starting with firstPtr). */ + int action; /* DLINE_UNLINK means DLines are + * currently linked into the list + * rooted at + * textPtr->dInfoPtr->dLinePtr and + * they have to be unlinked. + * DLINE_FREE means just free without + * unlinking. DLINE_FREE_TEMP means + * the DLine given is just a + * temporary one and we shouldn't + * invalidate anything for the + * overall widget. */ { register TkTextDispChunk *chunkPtr, *nextChunkPtr; register DLine *nextDLinePtr; - if (unlink) { + if (action == DLINE_UNLINK) { if (textPtr->dInfoPtr->dLinePtr == firstPtr) { textPtr->dInfoPtr->dLinePtr = lastPtr; } else { @@ -1645,7 +1898,9 @@ FreeDLines(textPtr, firstPtr, lastPtr, unlink) ckfree((char *) firstPtr); firstPtr = nextDLinePtr; } - textPtr->dInfoPtr->dLinesInvalidated = 1; + if (action != DLINE_FREE_TEMP) { + textPtr->dInfoPtr->dLinesInvalidated = 1; + } } /* @@ -1680,7 +1935,7 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) register TkTextDispChunk *chunkPtr; TextDInfo *dInfoPtr = textPtr->dInfoPtr; Display *display; - int height, x; + int height, y_off; if (dlPtr->chunkPtr == NULL) return; @@ -1710,8 +1965,8 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) if (textPtr->state == TK_TEXT_STATE_NORMAL) { for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); chunkPtr = chunkPtr->nextPtr) { - x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; if (chunkPtr->displayProc == TkTextInsertDisplayProc) { + int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, @@ -1725,7 +1980,6 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) * foreground information. Note: we have to call the displayProc * even for chunks that are off-screen. This is needed, for * example, so that embedded windows can be unmapped in this case. - * Conve */ for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); @@ -1738,54 +1992,59 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) continue; } - x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset; - if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { - /* - * Note: we have to call the displayProc even for chunks - * that are off-screen. This is needed, for example, so - * that embedded windows can be unmapped in this case. - * Display the chunk at a coordinate that can be clearly - * identified by the displayProc as being off-screen to - * the left (the displayProc may not be able to tell if - * something is off to the right). - */ + /* + * Don't call if elide. This tax ok since not very many + * visible DLine's in an area, but potentially many elide + * ones + */ + if (chunkPtr->displayProc != NULL) { + int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; - if (chunkPtr->displayProc != NULL) - (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width, - dlPtr->spaceAbove, - dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, - dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, - dlPtr->y + dlPtr->spaceAbove); - } else { - /* don't call if elide. This tax ok since not very many visible DLine's in - an area, but potentially many elide ones */ - if (chunkPtr->displayProc != NULL) + if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { + /* + * Note: we have to call the displayProc even for chunks + * that are off-screen. This is needed, for example, so + * that embedded windows can be unmapped in this case. + * Display the chunk at a coordinate that can be clearly + * identified by the displayProc as being off-screen to + * the left (the displayProc may not be able to tell if + * something is off to the right). + */ + x = -chunkPtr->width; + } (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, dlPtr->y + dlPtr->spaceAbove); } + if (dInfoPtr->dLinesInvalidated) { return; } } /* - * Copy the pixmap onto the screen. If this is the last line on - * the screen then copy a piece of the line, so that it doesn't - * overflow into the border area. Another special trick: copy the - * padding area to the left of the line; this is because the - * insertion cursor sometimes overflows onto that area and we want - * to get as much of the cursor as possible. + * Copy the pixmap onto the screen. If this is the first or last + * line on the screen then copy a piece of the line, so that it + * doesn't overflow into the border area. Another special trick: + * copy the padding area to the left of the line; this is because the + * insertion cursor sometimes overflows onto that area and we want to + * get as much of the cursor as possible. */ height = dlPtr->height; if ((height + dlPtr->y) > dInfoPtr->maxY) { height = dInfoPtr->maxY - dlPtr->y; } + if (dlPtr->y < dInfoPtr->y) { + y_off = dInfoPtr->y - dlPtr->y; + height -= y_off; + } else { + y_off = 0; + } XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC, - dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), - (unsigned) height, dInfoPtr->x, dlPtr->y); + dInfoPtr->x, y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x), + (unsigned) height, dInfoPtr->x, dlPtr->y + y_off); linesRedrawn++; } @@ -1857,7 +2116,7 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap) */ display = Tk_Display(textPtr->tkwin); - minX = dInfoPtr->curPixelOffset; + minX = dInfoPtr->curXPixelOffset; xOffset = dInfoPtr->x - minX; maxX = minX + dInfoPtr->maxX - dInfoPtr->x; chunkPtr = dlPtr->chunkPtr; @@ -2158,34 +2417,37 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap) /* *---------------------------------------------------------------------- * - * DisplayText -- + * TkTextAsyncUpdateLineMetrics -- * - * This procedure is invoked as a when-idle handler to update the - * display. It only redisplays the parts of the text widget that - * are out of date. + * This procedure is invoked as a background handler to update the + * pixel-height calculations of individual lines in an + * asychronous manner. + * + * Currently a timer-handler is used for this purpose, which + * continuously reschedules itself. It may well be better to + * use some other approach (e.g. a background thread). We can't + * use an idle-callback because of a known bug in Tcl/Tk in + * which idle callbacks are not allowed to re-schedule + * themselves. This just causes an effective infinite loop. * * Results: * None. * * Side effects: - * Information is redrawn on the screen. + * Line heights may be recalculated. * *---------------------------------------------------------------------- */ -static void -DisplayText(clientData) +void +TkTextAsyncUpdateLineMetrics(clientData) ClientData clientData; /* Information about widget. */ { register TkText *textPtr = (TkText *) clientData; TextDInfo *dInfoPtr = textPtr->dInfoPtr; - register DLine *dlPtr; - DLine *prevPtr; - Pixmap pixmap; - int maxHeight, borders; - int bottomY = 0; /* Initialization needed only to stop - * compiler warnings. */ - Tcl_Interp *interp; + int lineNum; + + dInfoPtr->lineUpdateTimer = NULL; if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { @@ -2193,141 +2455,862 @@ DisplayText(clientData) * The widget has been deleted. Don't do anything. */ + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); + } return; } - interp = textPtr->interp; - Tcl_Preserve((ClientData) interp); - - if (tkTextDebug) { - Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "", - TCL_GLOBAL_ONLY); - } - - if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { - - /* - * The widget has been deleted. Don't do anything. - */ - - goto end; - } - - if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) - || (dInfoPtr->maxY <= dInfoPtr->y)) { - UpdateDisplayInfo(textPtr); - dInfoPtr->flags &= ~REDRAW_PENDING; - goto doScrollbars; - } - numRedisplays++; - if (tkTextDebug) { - Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "", - TCL_GLOBAL_ONLY); - } - - if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { - - /* - * The widget has been deleted. Don't do anything. - */ - - goto end; + lineNum = dInfoPtr->currentMetricUpdateLine; + if (lineNum == -1) { + dInfoPtr->lastMetricUpdateLine = 0; } - - /* - * Choose a new current item if that is needed (this could cause - * event handlers to be invoked, hence the preserve/release calls - * and the loop, since the handlers could conceivably necessitate - * yet another current item calculation). The tkwin check is because - * the whole window could go away in the Tcl_Release call. + /* + * Update the lines in blocks of about 24 recalculations, + * or 250+ lines examined, so we pass in 256 for 'doThisMuch'. */ - - while (dInfoPtr->flags & REPICK_NEEDED) { - Tcl_Preserve((ClientData) textPtr); - dInfoPtr->flags &= ~REPICK_NEEDED; - TkTextPickCurrent(textPtr, &textPtr->pickEvent); - if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { - Tcl_Release((ClientData) textPtr); - goto end; + lineNum = TkTextUpdateLineMetrics(textPtr, lineNum, + dInfoPtr->lastMetricUpdateLine, 256); + if (lineNum == dInfoPtr->lastMetricUpdateLine) { + /* + * 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). + */ + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); } - Tcl_Release((ClientData) textPtr); + return; } - - /* - * First recompute what's supposed to be displayed. - */ - - UpdateDisplayInfo(textPtr); - dInfoPtr->dLinesInvalidated = 0; - - /* - * See if it's possible to bring some parts of the screen up-to-date - * by scrolling (copying from other parts of the screen). + dInfoPtr->currentMetricUpdateLine = lineNum; + /* + * Re-arm the timer. We already have a refCount on the text widget + * so no need to adjust that. */ + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateLineMetrics -- + * + * This procedure updates the pixel height calculations of a + * range of lines in the widget. The range is from lineNum to + * endLine, but, if doThisMuch is positive, then the procedure + * may return earlier, once a certain number of lines has been + * examined. The line counts are from 0. + * + * If doThisMuch is -1, then all lines in the range will be + * updated. This will potentially take quite some time for + * a large text widget. + * + * Note: with bad input for lineNum and endLine, this procedure can + * loop indefinitely. + * + * Results: + * The index of the last line examined (or -1 if we are about to + * wrap around from end to beginning of the widget, and the next + * line will be the first line). + * + * Side effects: + * Line heights may be recalculated. + * + *---------------------------------------------------------------------- + */ - for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - register DLine *dlPtr2; - int offset, height, y, oldY; - TkRegion damageRgn; - - if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY) - || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) { - continue; - } - - /* - * This line is already drawn somewhere in the window so it only - * needs to be copied to its new location. See if there's a group - * of lines that can all be copied together. - */ - - offset = dlPtr->y - dlPtr->oldY; - height = dlPtr->height; - y = dlPtr->y; - for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; - dlPtr2 = dlPtr2->nextPtr) { - if ((dlPtr2->oldY == -1) - || ((dlPtr2->oldY + offset) != dlPtr2->y) - || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { +int +TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) + TkText *textPtr; /* Information about widget. */ + int lineNum; /* Start at this line */ + int endLine; /* Go no further than this line */ + int doThisMuch; /* How many lines to check, or how many 10s of + * lines to recalculate. If '-1' then do + * everything in the range (which may take a + * while). */ +{ + TkTextLine *linePtr = NULL; + int count = 0; + int totalLines = TkBTreeNumLines(textPtr->tree); + + while (1) { + /* Get a suitable line */ + if (lineNum == -1 && linePtr == NULL) { + lineNum = 0; + linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + } else { + if (lineNum == -1 || linePtr == NULL) { + if (lineNum == -1) { + lineNum = 0; + } + linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + } else { + lineNum++; + linePtr = TkBTreeNextLine(linePtr); + } + if (lineNum == endLine) { + /* + * We have looped over all lines, so we're done. + */ break; } - height += dlPtr2->height; - } - - /* - * Reduce the height of the area being copied if necessary to - * avoid overwriting the border area. - */ - - if ((y + height) > dInfoPtr->maxY) { - height = dInfoPtr->maxY -y; } - oldY = dlPtr->oldY; - /* - * Update the lines we are going to scroll to show that they - * have been copied. - */ - - while (1) { - dlPtr->oldY = dlPtr->y; - if (dlPtr->nextPtr == dlPtr2) { + if (lineNum < totalLines) { + /* Now update the line's metrics if necessary */ + if (linePtr->pixelCalculationEpoch + != textPtr->dInfoPtr->lineMetricUpdateEpoch) { + /* + * Update the line and update the counter, counting + * 10 for each line we actually re-layout. + */ + TkTextUpdateOneLine(textPtr, linePtr); + count += 10; + } + } else { + /* + * We must never recalculate the height of the + * last artificial line. It must stay at zero, and + * if we recalculate it, it will change. + */ + + if (endLine >= totalLines) { break; } - dlPtr = dlPtr->nextPtr; + /* Set things up for the next loop through */ + lineNum = -1; } - - /* - * Scan through the lines following the copied ones to see if - * we are going to overwrite them with the copy operation. - * If so, mark them for redisplay. + count++; + + if (doThisMuch != -1 && count >= doThisMuch) { + break; + } + } + if (doThisMuch == -1) { + /* + * If we were requested to provide a full update, + * then also update the scrollbar. */ - + GetYView(textPtr->interp, textPtr, 1); + } + return lineNum; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextInvalidateLineMetrics -- + * + * Mark a number of text lines as having invalid line metric + * calculations. Never call this with linePtr as the last + * (artificial) line in the text. Depending on 'action' which + * indicates whether the given lines are simply invalid or have + * been inserted or deleted, the pre-existing asynchronous line + * update range may need to be adjusted. + * + * If linePtr is NULL then 'lineCount' and 'action' are ignored and + * all lines are invalidated. + * + * Results: + * None. + * + * Side effects: + * May schedule an asychronous callback. + * + *---------------------------------------------------------------------- + */ + +void +TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action) + TkText *textPtr; /* Widget record for text widget. */ + TkTextLine *linePtr; /* Invalidation starts from this line. */ + int lineCount; /* And includes this many following + * lines. */ + int action; /* Indicates what type of invalidation + * occurred (insert, delete, or + * simple). */ +{ + int fromLine; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + if (linePtr != NULL) { + int counter = lineCount; + + fromLine = TkBTreeLineIndex(linePtr); + + /* + * Invalid the height calculations of each line in the + * given range. + */ + linePtr->pixelCalculationEpoch = 0; + while (counter > 0 && linePtr != 0) { + linePtr = TkBTreeNextLine(linePtr); + if (linePtr != NULL) { + linePtr->pixelCalculationEpoch = 0; + } + counter--; + } + /* + * Now schedule an examination of each line in the union of the + * old and new update ranges, including the (possibly empty) + * range in between. If that between range is not-empty, then we + * are examining more lines than is strictly necessary (but the + * examination of the extra lines should be quick, since their + * pixelCalculationEpoch will be up to date). However, to keep + * track of that would require more complex record-keeping that + * what we have. + */ + if (dInfoPtr->lineUpdateTimer == NULL) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (action == TK_TEXT_INVALIDATE_DELETE) { + lineCount = 0; + } + dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1; + } else { + int toLine = fromLine + lineCount + 1; + + if (action == TK_TEXT_INVALIDATE_DELETE) { + if (toLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine -= lineCount; + } + } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (toLine <= dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine -= lineCount; + } + } else { + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } else if (action == TK_TEXT_INVALIDATE_INSERT) { + if (toLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine += lineCount; + } + } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + if (toLine <= dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine += lineCount; + } + if (toLine > dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } else { + if (dInfoPtr->lastMetricUpdateLine != -1) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } else { + if (fromLine < dInfoPtr->currentMetricUpdateLine) { + dInfoPtr->currentMetricUpdateLine = fromLine; + } + if (dInfoPtr->lastMetricUpdateLine != -1 + && toLine > dInfoPtr->lastMetricUpdateLine) { + dInfoPtr->lastMetricUpdateLine = toLine; + } + } + } + } else { + /* + * This invalidates the height of all lines in the widget. + */ + if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) { + dInfoPtr->lineMetricUpdateEpoch++; + } + /* + * This has the effect of forcing an entire new loop + * of update checks on all lines in the widget. + */ + if (dInfoPtr->lineUpdateTimer == NULL) { + dInfoPtr->currentMetricUpdateLine = -1; + } + dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine; + } + + /* + * Now re-set the current update calculations + */ + if (dInfoPtr->lineUpdateTimer == NULL) { + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextFindDisplayLineEnd -- + * + * This procedure is invoked to find the index of the beginning or + * end of the particular display line on which the given index sits, + * whether that line is displayed or not. + * + * If 'end' is zero, we look for the start, and if 'end' is one + * we look for the end. + * + * If the beginning of the current display line is elided, and we + * are looking for the start of the line, then the returned index + * will be the first elided index on the display line. + * + * Similarly if the end of the current display line is elided + * and we are looking for the end, then the returned index will + * be the last elided index on the display line. (NB. This also + * highlights a current bug in the text widget that we cannot + * place two logical lines on a single display line -- even + * though the newline in this case is elided, it still causes + * a line break to be shown). + * + * Results: + * Modifies indexPtr to point to the given end. + * + * If xOffset is non-NULL, it is set to the x-pixel offset of the + * given original index within the given display line. + * + * Side effects: + * The combination of 'LayoutDLine' and 'FreeDLines' seems + * like a rather time-consuming way of gathering the information + * we need, so this would be a good place to look to speed up + * the calculations. In particular these calls will map and + * unmap embedded windows respectively, which I would hope isn't + * exactly necessary! + * + *---------------------------------------------------------------------- + */ + +void +TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *indexPtr; /* Index we will adjust to the display + * line start or end. */ + int end; /* 0 = start, 1 = end. */ + int *xOffset; /* NULL, or used to store the x-pixel + * offset of the original index within + * its display line. */ +{ + if (!end && indexPtr->byteIndex == 0) { + /* Nothing to do */ + if (xOffset != NULL) { + *xOffset = 0; + } + return; + } else { + TkTextIndex index = *indexPtr; + index.byteIndex = 0; + index.textPtr = NULL; + + while (1) { + DLine *dlPtr; + int byteCount; + + dlPtr = LayoutDLine(textPtr, &index); + byteCount = dlPtr->byteCount; + + /* + * 'byteCount' goes up to the beginning of the next line, + * so equality here says we need one more line + */ + if (index.byteIndex + byteCount > indexPtr->byteIndex) { + /* It's on this display line */ + if (xOffset != NULL) { + /* + * This call takes a byte index relative to the + * start of the current _display_ line, not + * logical line. We are about to overwrite + * indexPtr->byteIndex, so we must do this now. + */ + *xOffset = DlineXOfIndex(textPtr, dlPtr, + indexPtr->byteIndex - dlPtr->index.byteIndex); + } + indexPtr->byteIndex = index.byteIndex; + if (end) { + /* + * The index we want is one less than the number + * of bytes in the display line. + */ + indexPtr->byteIndex += byteCount - sizeof(char); + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + return; + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + TkTextIndexForwBytes(&index, byteCount, &index); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TextCalculateDisplayLineHeight -- + * + * This procedure is invoked to recalculate the height of the + * particular display line which starts with the given index, + * whether that line is displayed or not. + * + * This procedure does not, in itself, update any cached + * information about line heights. That should be done, where + * necessary, by its callers. + * + * The behaviour of this procedure is _undefined_ if indexPtr + * is not currently at the beginning of a display line. + * + * Results: + * The number of vertical pixels used by the display line. + * + * If 'byteCountPtr' is non-NULL, then returns in that pointer + * the number of byte indices on the given display line (which + * can be used to update indexPtr in a loop). + * + * Side effects: + * The combination of 'LayoutDLine' and 'FreeDLines' seems + * like a rather time-consuming way of gathering the information + * we need, so this would be a good place to look to speed up + * the calculations. In particular these calls will map and + * unmap embedded windows respectively, which I would hope isn't + * exactly necessary! + * + *---------------------------------------------------------------------- + */ + +static int +TextCalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr) + TkText *textPtr; /* Widget record for text widget. */ + CONST TkTextIndex *indexPtr; /* The index at the beginning of the + * display line of interest. */ + int *byteCountPtr; /* NULL or used to return the number of + * byte indices on the given display + * line. */ +{ + DLine *dlPtr; + int pixelHeight; + + /* + * Layout, find the information we need and then free the + * display-line we laid-out. We must use 'FreeDLines' because it + * will actually call the relevant code to unmap any embedded windows + * which were mapped in the LayoutDLine call! + */ + dlPtr = LayoutDLine(textPtr, indexPtr); + pixelHeight = dlPtr->height; + if (byteCountPtr != NULL) { + *byteCountPtr = dlPtr->byteCount; + } + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); + + return pixelHeight; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextIndexYPixels -- + * + * This procedure is invoked to calculate the number of vertical + * pixels between the first index of the text widget and the given + * index. The range from first logical line to given logical + * line is determined using the cached values, and the range + * inside the given logical line is calculated on the fly. + * + * Results: + * The pixel distance between first pixel in the widget and the + * top of the index's current display line (could be zero). + * + * Side effects: + * Just those of 'TextCalculateDisplayLineHeight'. + * + *---------------------------------------------------------------------- + */ + +int +TkTextIndexYPixels(textPtr, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + CONST TkTextIndex *indexPtr; /* The index of which we want the pixel + * distance from top of logical line to + * top of index. */ +{ + int pixelHeight; + TkTextIndex index; + + pixelHeight = TkBTreePixels(indexPtr->linePtr); + + /* + * Iterate through all display-lines corresponding to the single + * logical line belonging to indexPtr, adding up the pixel height of + * each such display line as we go along, until we go past + * 'indexPtr'. + */ + + if (indexPtr->byteIndex == 0) { + return pixelHeight; + } + + index.tree = textPtr->tree; + index.linePtr = indexPtr->linePtr; + index.byteIndex = 0; + index.textPtr = NULL; + + while (1) { + int bytes, height; + + /* + * Currently this call doesn't have many side-effects. + * However, if in the future we change the code so there + * are side-effects (such as adjusting linePtr->pixelHeight), + * then the code might not quite work as intended, + * specifically the 'linePtr->pixelHeight == pixelHeight' test + * below this while loop. + */ + height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes); + + index.byteIndex += bytes; + + if (index.byteIndex > indexPtr->byteIndex) { + return pixelHeight; + } + + if (height > 0) { + pixelHeight += height; + } + + if (index.byteIndex == indexPtr->byteIndex) { + return pixelHeight; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateOneLine -- + * + * This procedure is invoked to recalculate the height of a + * particular logical line, whether that line is displayed or not. + * + * It must NEVER be called for the artificial last TkTextLine + * which is used internally for administrative purposes only. + * That line must retain its initial height of 0 otherwise + * the pixel height calculation maintained by the B-tree will + * be wrong. + * + * Results: + * The number of display lines in the logical line. This could + * be zero if the line is totally elided. + * + * Side effects: + * Line heights may be recalculated, and a timer to update + * the scrollbar may be installed. Also see the called + * function 'TextCalculateDisplayLineHeight' for its side + * effects. + * + *---------------------------------------------------------------------- + */ + +int +TkTextUpdateOneLine(textPtr, linePtr) + TkText *textPtr; /* Widget record for text widget. */ + TkTextLine *linePtr; /* The line of which to calculate the + * height. */ +{ + TkTextIndex index; + int pixelHeight, displayLines; + + index.tree = textPtr->tree; + index.linePtr = linePtr; + index.byteIndex = 0; + index.textPtr = NULL; + + /* + * Iterate through all display-lines corresponding to the + * single logical line 'linePtr', adding up the pixel height + * of each such display line as we go along. The final + * total is, therefore, the height of the logical line. + */ + + pixelHeight = 0; + displayLines = 0; + + while (1) { + int bytes, height; + + /* + * Currently this call doesn't have many side-effects. + * However, if in the future we change the code so there + * are side-effects (such as adjusting linePtr->pixelHeight), + * then the code might not quite work as intended, + * specifically the 'linePtr->pixelHeight == pixelHeight' test + * below this while loop. + */ + height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes); + + if (height > 0) { + pixelHeight += height; + displayLines++; + } + + if (TkTextIndexForwBytes(&index, bytes, &index)) { + break; + } + + if (index.linePtr != linePtr) { + break; + } + } + + /* + * Mark the logical line as being up to date (caution: it isn't + * yet up to date, that will happen in TkBTreeAdjustPixelHeight + * just below). + */ + linePtr->pixelCalculationEpoch = textPtr->dInfoPtr->lineMetricUpdateEpoch; + + if (linePtr->pixelHeight == pixelHeight) { + return displayLines; + } + + /* + * We now use the resulting 'pixelHeight' to refer to the + * height of the entire widget, which may be used just below + * for reporting/debugging purposes + */ + pixelHeight = TkBTreeAdjustPixelHeight(linePtr, pixelHeight); + + if (tkTextDebug) { + char buffer[TCL_INTEGER_SPACE + 1]; + + if (TkBTreeNextLine(linePtr) == NULL) { + panic("Mustn't ever update line height of last artificial line"); + } + + sprintf(buffer, "%d", pixelHeight); + Tcl_SetVar2(textPtr->interp, "tk_textNumPixels", (char *) NULL, + buffer, TCL_GLOBAL_ONLY); + } + if (textPtr->dInfoPtr->scrollbarTimer == NULL) { + textPtr->refCount++; + textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200, + TkTextUpdateYScrollbar, (ClientData) textPtr); + } + return displayLines; +} + +/* + *---------------------------------------------------------------------- + * + * DisplayText -- + * + * This procedure is invoked as a when-idle handler to update the + * display. It only redisplays the parts of the text widget that + * are out of date. + * + * Results: + * None. + * + * Side effects: + * Information is redrawn on the screen. + * + *---------------------------------------------------------------------- + */ + +static void +DisplayText(clientData) + ClientData clientData; /* Information about widget. */ +{ + register TkText *textPtr = (TkText *) clientData; + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register DLine *dlPtr; + DLine *prevPtr; + Pixmap pixmap; + int maxHeight, borders; + int bottomY = 0; /* Initialization needed only to stop + * compiler warnings. */ + Tcl_Interp *interp; + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + + /* + * The widget has been deleted. Don't do anything. + */ + + return; + } + + interp = textPtr->interp; + Tcl_Preserve((ClientData) interp); + + if (tkTextDebug) { + Tcl_SetVar2(interp, "tk_textRelayout", (char *) NULL, "", + TCL_GLOBAL_ONLY); + } + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x) + || (dInfoPtr->maxY <= dInfoPtr->y)) { + UpdateDisplayInfo(textPtr); + dInfoPtr->flags &= ~REDRAW_PENDING; + goto doScrollbars; + } + numRedisplays++; + if (tkTextDebug) { + Tcl_SetVar2(interp, "tk_textRedraw", (char *) NULL, "", + TCL_GLOBAL_ONLY); + } + + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + + /* + * The widget has been deleted. Don't do anything. + */ + + goto end; + } + + /* + * Choose a new current item if that is needed (this could cause + * event handlers to be invoked, hence the preserve/release calls + * and the loop, since the handlers could conceivably necessitate + * yet another current item calculation). The tkwin check is because + * the whole window could go away in the Tcl_Release call. + */ + + while (dInfoPtr->flags & REPICK_NEEDED) { + textPtr->refCount++; + dInfoPtr->flags &= ~REPICK_NEEDED; + TkTextPickCurrent(textPtr, &textPtr->pickEvent); + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); + goto end; + } + if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { + goto end; + } + } + + /* + * First recompute what's supposed to be displayed. + */ + + UpdateDisplayInfo(textPtr); + dInfoPtr->dLinesInvalidated = 0; + + /* + * See if it's possible to bring some parts of the screen up-to-date + * by scrolling (copying from other parts of the screen). We have + * to be particularly careful with the top and bottom lines of the + * display, since these may only be partially visible and therefore + * not helpful for some scrolling purposes. + */ + + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { + register DLine *dlPtr2; + int offset, height, y, oldY; + TkRegion damageRgn; + + /* + * These tests are, in order: + * + * 1. If the line is already marked as invalid + * 2. If the line hasn't moved + * 3. If the line overlaps the bottom of the window and we + * are scrolling up + * 4. If the line overlaps the top of the window and we are + * scrolling down + * + * If any of these tests are true, then we can't scroll this + * line's part of the display. + * + * Note that even if tests 3 or 4 aren't true, we may be + * able to scroll the line, but we still need to be sure + * to call embedded window display procs on top and bottom + * lines if they have any portion non-visible (see below). + */ + if ((dlPtr->flags & OLD_Y_INVALID) + || (dlPtr->y == dlPtr->oldY) + || (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY) + && (dlPtr->y < dlPtr->oldY)) + || ((dlPtr->oldY < dInfoPtr->y) + && (dlPtr->y > dlPtr->oldY))) { + continue; + } + + /* + * This line is already drawn somewhere in the window so it only + * needs to be copied to its new location. See if there's a group + * of lines that can all be copied together. + */ + + offset = dlPtr->y - dlPtr->oldY; + height = dlPtr->height; + y = dlPtr->y; + for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL; + dlPtr2 = dlPtr2->nextPtr) { + if ((dlPtr2->flags & OLD_Y_INVALID) + || ((dlPtr2->oldY + offset) != dlPtr2->y) + || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) { + break; + } + height += dlPtr2->height; + } + + /* + * Reduce the height of the area being copied if necessary to + * avoid overwriting the border area. + */ + if ((y + height) > dInfoPtr->maxY) { + height = dInfoPtr->maxY -y; + } + oldY = dlPtr->oldY; + if (y < dInfoPtr->y) { + /* + * Adjust if the area being copied is going to overwrite + * the top border of the window (so the top line is only + * half onscreen). + */ + int y_off = dInfoPtr->y - dlPtr->y; + height -= y_off; + oldY += y_off; + y = dInfoPtr->y; + } + + /* + * Update the lines we are going to scroll to show that they + * have been copied. + */ + + while (1) { + /* The DLine already has OLD_Y_INVALID cleared */ + dlPtr->oldY = dlPtr->y; + if (dlPtr->nextPtr == dlPtr2) { + break; + } + dlPtr = dlPtr->nextPtr; + } + + /* + * Scan through the lines following the copied ones to see if + * we are going to overwrite them with the copy operation. + * If so, mark them for redisplay. + */ + for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) { - if ((dlPtr2->oldY != -1) + if ((!(dlPtr2->flags & OLD_Y_INVALID)) && ((dlPtr2->oldY + dlPtr2->height) > y) && (dlPtr2->oldY < (y + height))) { - dlPtr2->oldY = -1; + dlPtr2->flags |= OLD_Y_INVALID; } } @@ -2439,14 +3422,23 @@ DisplayText(clientData) maxHeight = -1; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) { + if ((dlPtr->height > maxHeight) + && ((dlPtr->flags & OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) { maxHeight = dlPtr->height; } bottomY = dlPtr->y + dlPtr->height; } - if (maxHeight > dInfoPtr->maxY) { - maxHeight = dInfoPtr->maxY; + /* + * There used to be a line here which restricted 'maxHeight' to be no + * larger than 'dInfoPtr->maxY', but this is incorrect for the case + * where individual lines may be taller than the widget _and_ we have + * smooth scrolling. What we can do is restrict maxHeight to be + * no larger than 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'. + */ + if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) { + maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset); } + if (maxHeight > 0) { pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin), Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin), @@ -2455,7 +3447,7 @@ DisplayText(clientData) (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY); prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) { if (dlPtr->chunkPtr == NULL) continue; - if (dlPtr->oldY != dlPtr->y) { + if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) { if (tkTextDebug) { char string[TK_POS_CHARS]; TkTextPrintIndex(&dlPtr->index, string); @@ -2469,9 +3461,53 @@ DisplayText(clientData) return; } dlPtr->oldY = dlPtr->y; - dlPtr->flags &= ~NEW_LAYOUT; + dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID); + } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0) + || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) { + register TkTextDispChunk *chunkPtr; + + /* + * It's the first or last DLine which are also + * overlapping the top or bottom of the window, but we + * decided above it wasn't necessary to display them (we + * were able to update them by scrolling). This is fine, + * except that if the lines contain any embedded windows, + * we must still call the display proc on them because + * they might need to be unmapped or they might need to + * be moved to reflect their new position. Otherwise, + * everything else moves, but the embedded window + * doesn't! + * + * So, we loop through all the chunks, calling the + * display proc of embedded windows only. + */ + for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL); + chunkPtr = chunkPtr->nextPtr) { + int x; + if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) { + continue; + } + x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; + if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) { + /* + * Note: we have to call the displayProc even for + * chunks that are off-screen. This is needed, + * for example, so that embedded windows can be + * unmapped in this case. Display the chunk at a + * coordinate that can be clearly identified by + * the displayProc as being off-screen to the + * left (the displayProc may not be able to tell + * if something is off to the right). + */ + x = -chunkPtr->width; + } + TkTextEmbWinDisplayProc(chunkPtr, x, dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, (Display *) NULL, + (Drawable) None, dlPtr->y + dlPtr->spaceAbove); + } + } - /*prevPtr = dlPtr;*/ } Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap); } @@ -2658,9 +3694,11 @@ TextInvalidateRegion(textPtr, region) maxY = rect.y + rect.height; for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { - if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y, - rect.width, (unsigned int) dlPtr->height) != RectangleOut)) { - dlPtr->oldY = -1; + if ((!(dlPtr->flags & OLD_Y_INVALID)) + && (TkRectInRegion(region, rect.x, dlPtr->y, + rect.width, (unsigned int) dlPtr->height) + != RectangleOut)) { + dlPtr->flags |= OLD_Y_INVALID; } } if (dInfoPtr->topOfEof < maxY) { @@ -2693,6 +3731,10 @@ TextInvalidateRegion(textPtr, region) * changed). This procedure must be called *before* a change is * made, so that indexes in the display information are still * valid. + * + * Note: if the range of indices may change geometry as well + * as simply requiring redisplay, then the caller should also + * call TkTextInvalidateLineMetrics. * * Results: * None. @@ -2707,10 +3749,11 @@ TextInvalidateRegion(textPtr, region) void TkTextChanged(textPtr, index1Ptr, index2Ptr) - TkText *textPtr; /* Widget record for text widget. */ - TkTextIndex *index1Ptr; /* Index of first character to redisplay. */ - TkTextIndex *index2Ptr; /* Index of character just after last one - * to redisplay. */ + TkText *textPtr; /* Widget record for text widget. */ + CONST TkTextIndex *index1Ptr; /* Index of first character to + * redisplay. */ + CONST TkTextIndex *index2Ptr; /* Index of character just after last one + * to redisplay. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; DLine *firstPtr, *lastPtr; @@ -2763,7 +3806,7 @@ TkTextChanged(textPtr, index1Ptr, index2Ptr) * Delete all the DLines from firstPtr up to but not including lastPtr. */ - FreeDLines(textPtr, firstPtr, lastPtr, 1); + FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK); } /* @@ -2806,6 +3849,31 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) TkTextIndex *curIndexPtr; TkTextIndex endOfText, *endIndexPtr; + /* + * Invalidate the pixel calculation of all lines in the given range. + * This may be a bit over-aggressive, so we could consider more + * subtle techniques here in the future. In particular, when we + * create a tag for the first time with '.t tag configure foo -font + * "Arial 20"', say, even though that obviously can't apply to + * anything at all (the tag didn't exist a moment ago), we invalidate + * every single line in the widget. + */ + if (tagPtr->affectsDisplayGeometry) { + TkTextLine *startLine, *endLine; + if (index1Ptr == NULL) { + startLine = NULL; + } else { + startLine = index1Ptr->linePtr; + } + if (index2Ptr == NULL) { + endLine = NULL; + } else { + endLine = index2Ptr->linePtr; + } + TkTextInvalidateLineMetrics(textPtr, startLine, endLine - startLine, + TK_TEXT_INVALIDATE_ONLY); + } + /* * Round up the starting position if it's before the first line * visible on the screen (we only care about what's on the screen). @@ -2912,7 +3980,7 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) * be re-layed out and redrawn. */ - FreeDLines(textPtr, dlPtr, endPtr, 1); + FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK); dlPtr = endPtr; /* @@ -2947,8 +4015,10 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) */ void -TkTextRelayoutWindow(textPtr) +TkTextRelayoutWindow(textPtr, mask) TkText *textPtr; /* Widget record for text widget. */ + int mask; /* OR'd collection of bits showing what + * has changed */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; GC new; @@ -2981,7 +4051,7 @@ TkTextRelayoutWindow(textPtr) * Throw away all the current layout information. */ - FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1); + FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK); dInfoPtr->dLinePtr = NULL; /* @@ -3016,7 +4086,7 @@ TkTextRelayoutWindow(textPtr) */ if (textPtr->topIndex.byteIndex != 0) { - MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex); + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); } /* @@ -3026,6 +4096,25 @@ TkTextRelayoutWindow(textPtr) dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1; dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1; + + if (mask & TK_TEXT_LINE_GEOMETRY) { + /* + * Set up line metric recalculation. + * + * Avoid the special zero value, since that is used to + * mark individual lines as being out of date. + */ + if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) { + dInfoPtr->lineMetricUpdateEpoch++; + } + dInfoPtr->currentMetricUpdateLine = -1; + + if (dInfoPtr->lineUpdateTimer == NULL) { + textPtr->refCount++; + dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, + TkTextAsyncUpdateLineMetrics, (ClientData) textPtr); + } + } } /* @@ -3052,17 +4141,27 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) TkText *textPtr; /* Widget record for text widget. */ TkTextIndex *indexPtr; /* Position that is to appear somewhere * in the view. */ - int pickPlace; /* 0 means topLine must appear at top of - * screen. 1 means we get to pick where it - * appears: minimize screen motion or else - * display line at center of screen. */ + int pickPlace; /* 0 means the given index must appear + * exactly at the top of the screen. + * TK_TEXT_PICKPLACE (-1) means we get to + * pick where it appears: minimize screen + * motion or else display line at center + * of screen. TK_TEXT_NOPIXELADJUST (-2) + * indicates to make the given index the + * top line, but if it is already the top + * line, don't nudge it up or down by a + * few pixels just to make sure it is + * entirely displayed. Positive numbers + * indicate the number of pixels of the + * index's line which are to be off the + * top of the screen. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; register DLine *dlPtr; int bottomY, close, lineIndex; TkTextIndex tmpIndex, rounded; - Tk_FontMetrics fm; - + int lineHeight; + /* * If the specified position is the extra line at the end of the * text, round it back to the last real line. @@ -3070,11 +4169,20 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) lineIndex = TkBTreeLineIndex(indexPtr->linePtr); if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { - TkTextIndexBackChars(indexPtr, 1, &rounded); + TkTextIndexBackChars(indexPtr, 1, &rounded, COUNT_INDICES); indexPtr = &rounded; } - if (!pickPlace) { + if (pickPlace == -2) { + if (textPtr->topIndex.linePtr == indexPtr->linePtr + && textPtr->topIndex.byteIndex == indexPtr->byteIndex) { + pickPlace = dInfoPtr->topPixelOffset; + } else { + pickPlace = 0; + } + } + + if (pickPlace != -1) { /* * The specified position must go at the top of the screen. * Just leave all the DLine's alone: we may be able to reuse @@ -3082,11 +4190,11 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * without redisplaying it all. */ - if (indexPtr->byteIndex == 0) { - textPtr->topIndex = *indexPtr; - } else { - MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); + textPtr->topIndex = *indexPtr; + if (indexPtr->byteIndex != 0) { + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); } + dInfoPtr->newTopPixelOffset = pickPlace; goto scheduleUpdate; } @@ -3111,6 +4219,16 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) dlPtr = NULL; } else if ((dlPtr->index.linePtr == indexPtr->linePtr) && (dlPtr->index.byteIndex <= indexPtr->byteIndex)) { + if (dInfoPtr->dLinePtr == dlPtr + && dInfoPtr->topPixelOffset != 0) { + /* + * It is on the top line, but that line is hanging + * off the top of the screen. Change the top + * overlap to zero and update. + */ + dInfoPtr->newTopPixelOffset = 0; + goto scheduleUpdate; + } return; } } @@ -3119,37 +4237,50 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * The desired line isn't already on-screen. Figure out what * it means to be "close" to the top or bottom of the screen. * Close means within 1/3 of the screen height or within three - * lines, whichever is greater. Add one extra line also, to - * account for the way MeasureUp rounds. + * lines, whichever is greater. + * + * If the line is not close, place it in the center of the + * window. */ - Tk_GetFontMetrics(textPtr->tkfont, &fm); - bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2; + lineHeight = TextCalculateDisplayLineHeight(textPtr, indexPtr, NULL); + /* + * It would be better if 'bottomY' were calculated using the + * actual height of the given line, not 'textPtr->charHeight'. + */ + bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2; close = (dInfoPtr->maxY - dInfoPtr->y)/3; - if (close < 3*fm.linespace) { - close = 3*fm.linespace; + if (close < 3*textPtr->charHeight) { + close = 3*textPtr->charHeight; } - close += fm.linespace; if (dlPtr != NULL) { + int overlap; /* * The desired line is above the top of screen. If it is * "close" to the top of the window then make it the top - * line on the screen. + * line on the screen. MeasureUp counts from the bottom + * of the given index upwards, so we add an extra half line + * to be sure we count far enough. */ - MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex); + MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2, + &tmpIndex, &overlap); if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) { - MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex); + textPtr->topIndex = *indexPtr; + TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL); + dInfoPtr->newTopPixelOffset = 0; goto scheduleUpdate; } } else { + int overlap; /* * The desired line is below the bottom of the screen. If it is * "close" to the bottom of the screen then position it at the * bottom of the screen. */ - MeasureUp(textPtr, indexPtr, close, &tmpIndex); + MeasureUp(textPtr, indexPtr, close + lineHeight + - textPtr->charHeight/2, &tmpIndex, &overlap); if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) { bottomY = dInfoPtr->maxY - dInfoPtr->y; } @@ -3163,7 +4294,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * is a half-line lower than the center of the window. */ - MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex); + MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex, + &dInfoPtr->newTopPixelOffset); scheduleUpdate: if (!(dInfoPtr->flags & REDRAW_PENDING)) { @@ -3175,11 +4307,72 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) /* *-------------------------------------------------------------- * - * MeasureUp -- + * TkTextMeasureDown -- * * Given one index, find the index of the first character * on the highest display line that would be displayed no more - * than "distance" pixels above the given index. + * than "distance" pixels below the top of the given index. + * + * Results: + * The srcPtr is manipulated in place to reflect the new + * position. We return the number of pixels by which 'distance' + * overlaps the srcPtr. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +int +TkTextMeasureDown(textPtr, srcPtr, distance) + TkText *textPtr; /* Text widget in which to measure. */ + TkTextIndex *srcPtr; /* Index of character from which to start + * measuring. */ + int distance; /* Vertical distance in pixels measured + * from the top pixel in srcPtr's + logical line. */ +{ + TkTextLine *lastLinePtr; + DLine *dlPtr; + TkTextIndex loop; + + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + + do { + dlPtr = LayoutDLine(textPtr, srcPtr); + dlPtr->nextPtr = NULL; + + if (distance < dlPtr->height) { + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); + break; + } + distance -= dlPtr->height; + TkTextIndexForwBytes(srcPtr, dlPtr->byteCount, &loop); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); + if (loop.linePtr == lastLinePtr) { + break; + } + *srcPtr = loop; + } while (distance > 0); + + return distance; +} + +/* + *-------------------------------------------------------------- + * + * MeasureUp -- + * + * Given one index, find the index of the first character on the + * highest display line that would be displayed no more than + * "distance" pixels above the given index. + * + * If this function is called with distance=0, it simply finds the + * first index on the same display line as srcPtr. However, there + * is a another function TkTextFindDisplayLineEnd designed just for + * that task which is probably better to use. * * Results: * *dstPtr is filled in with the index of the first character @@ -3187,9 +4380,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * up "distance" pixels above the pixel just below an imaginary * display line that contains srcPtr. If the display line * that covers this coordinate actually extends above the - * coordinate, then return the index of the next lower line - * instead (i.e. the returned index will be completely visible - * at or below the given y-coordinate). + * coordinate, then return any excess pixels in *overlap, if + * that is non-NULL. * * Side effects: * None. @@ -3198,30 +4390,31 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) */ static void -MeasureUp(textPtr, srcPtr, distance, dstPtr) +MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap) TkText *textPtr; /* Text widget in which to measure. */ - TkTextIndex *srcPtr; /* Index of character from which to start + CONST TkTextIndex *srcPtr; /* Index of character from which to start * measuring. */ int distance; /* Vertical distance in pixels measured * from the pixel just below the lowest * one in srcPtr's line. */ TkTextIndex *dstPtr; /* Index to fill in with result. */ + int *overlap; /* Used to store how much of the final + * index returned was not covered by + * 'distance'. */ { int lineNum; /* Number of current line. */ int bytesToCount; /* Maximum number of bytes to measure in * current line. */ - TkTextIndex bestIndex; /* Best candidate seen so far for result. */ TkTextIndex index; DLine *dlPtr, *lowestPtr; - int noBestYet; /* 1 means bestIndex hasn't been set. */ - noBestYet = 1; bytesToCount = srcPtr->byteIndex + 1; index.tree = srcPtr->tree; for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0; lineNum--) { /* * Layout an entire text line (potentially > 1 display line). + * * For the first line, which contains srcPtr, only layout the * part up through srcPtr (bytesToCount is non-infinite to * accomplish this). Make a list of all the display lines @@ -3243,17 +4436,20 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr) /* * Scan through the display lines to see if we've covered enough * vertical distance. If so, save the starting index for the - * line at the desired location. + * line at the desired location. If distance was zero to start + * with then we simply get the first index on the same display + * line as the original index. */ for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) { distance -= dlPtr->height; - if (distance < 0) { - *dstPtr = (noBestYet) ? dlPtr->index : bestIndex; + if (distance <= 0) { + *dstPtr = dlPtr->index; + if (overlap != NULL) { + *overlap = -distance; + } break; } - bestIndex = dlPtr->index; - noBestYet = 0; } /* @@ -3261,8 +4457,8 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr) * for the next display line to lay out. */ - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); - if (distance < 0) { + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE); + if (distance <= 0) { return; } bytesToCount = INT_MAX; /* Consider all chars. in next line. */ @@ -3274,6 +4470,9 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr) */ TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr); + if (overlap != NULL) { + *overlap = 0; + } } /* @@ -3310,8 +4509,7 @@ TkTextSeeCmd(textPtr, interp, objc, objv) TkTextDispChunk *chunkPtr; if (objc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - Tcl_GetString(objv[0]), " see index\"", (char *) NULL); + Tcl_WrongNumArgs(interp, 2, objv, "index"); return TCL_ERROR; } if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) { @@ -3324,14 +4522,14 @@ TkTextSeeCmd(textPtr, interp, objc, objv) */ if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { - TkTextIndexBackChars(&index, 1, &index); + TkTextIndexBackChars(&index, 1, &index, COUNT_INDICES); } /* * First get the desired position into the vertical range of the window. */ - TkTextSetYView(textPtr, &index, 1); + TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE); /* * Now make sure that the character is in view horizontally. @@ -3376,24 +4574,24 @@ TkTextSeeCmd(textPtr, interp, objc, objv) dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, &height); - delta = x - dInfoPtr->curPixelOffset; + delta = x - dInfoPtr->curXPixelOffset; oneThird = lineWidth/3; if (delta < 0) { if (delta < -oneThird) { - dInfoPtr->newByteOffset = (x - lineWidth/2) + dInfoPtr->newXByteOffset = (x - lineWidth/2) / textPtr->charWidth; } else { - dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1) + dInfoPtr->newXByteOffset -= ((-delta) + textPtr->charWidth - 1) / textPtr->charWidth; } } else { delta -= (lineWidth - width); if (delta > 0) { if (delta > oneThird) { - dInfoPtr->newByteOffset = (x - lineWidth/2) + dInfoPtr->newXByteOffset = (x - lineWidth/2) / textPtr->charWidth; } else { - dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1) + dInfoPtr->newXByteOffset += (delta + textPtr->charWidth - 1) / textPtr->charWidth; } } else { @@ -3449,7 +4647,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv) return TCL_OK; } - newOffset = dInfoPtr->newByteOffset; + newOffset = dInfoPtr->newXByteOffset; type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count); switch (type) { case TK_SCROLL_ERROR: @@ -3461,35 +4659,115 @@ TkTextXviewCmd(textPtr, interp, objc, objv) if (fraction < 0) { fraction = 0; } - newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth) - + 0.5); - break; - case TK_SCROLL_PAGES: - charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) - - 2; - if (charsPerPage < 1) { - charsPerPage = 1; + newOffset = (int) (((fraction * dInfoPtr->maxLength) + / textPtr->charWidth) + 0.5); + break; + case TK_SCROLL_PAGES: + charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth) + - 2; + if (charsPerPage < 1) { + charsPerPage = 1; + } + newOffset += charsPerPage * count; + break; + case TK_SCROLL_UNITS: + newOffset += count; + break; + } + + dInfoPtr->newXByteOffset = newOffset; + dInfoPtr->flags |= DINFO_OUT_OF_DATE; + if (!(dInfoPtr->flags & REDRAW_PENDING)) { + dInfoPtr->flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * YScrollByPixels -- + * + * This procedure is called to scroll a text widget up or down + * by a given number of pixels. + * + * Results: + * None. + * + * Side effects: + * The view in textPtr's window changes to reflect the value + * of "offset". + * + *---------------------------------------------------------------------- + */ + +static void +YScrollByPixels(textPtr, offset) + TkText *textPtr; /* Widget to scroll. */ + int offset; /* Amount by which to scroll, in + * pixels. Positive means that information + * later in text becomes visible, negative + * means that information earlier in the + * text becomes visible. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + + if (offset < 0) { + /* + * Now we want to measure up this number of pixels + * from the top of the screen. But the top line may + * not be totally visible. Note that 'count' is + * negative here. + */ + offset -= TextCalculateDisplayLineHeight(textPtr, + &textPtr->topIndex, NULL) - dInfoPtr->topPixelOffset; + MeasureUp(textPtr, &textPtr->topIndex, -offset, + &textPtr->topIndex, &dInfoPtr->newTopPixelOffset); + } else if (offset > 0) { + DLine *dlPtr; + TkTextLine *lastLinePtr; + TkTextIndex new; + /* + * Scrolling down by pixels. Layout lines starting at + * the top index and count through the desired vertical + * distance. + */ + + lastLinePtr = TkBTreeFindLine(textPtr->tree, + TkBTreeNumLines(textPtr->tree)); + offset += dInfoPtr->topPixelOffset; + dInfoPtr->newTopPixelOffset = 0; + while (offset > 0) { + dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); + dlPtr->nextPtr = NULL; + TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, + &new); + if (offset <= dlPtr->height) { + /* Adjust the top overlap accordingly */ + dInfoPtr->newTopPixelOffset = offset; + } + offset -= dlPtr->height; + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); + if (new.linePtr == lastLinePtr || offset <= 0) { + break; } - newOffset += charsPerPage * count; - break; - case TK_SCROLL_UNITS: - newOffset += count; - break; + textPtr->topIndex = new; + } + } else { + /* offset = 0, so no scrolling required */ + return; } - - dInfoPtr->newByteOffset = newOffset; - dInfoPtr->flags |= DINFO_OUT_OF_DATE; if (!(dInfoPtr->flags & REDRAW_PENDING)) { - dInfoPtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); } - return TCL_OK; + dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; } /* *---------------------------------------------------------------------- * - * ScrollByLines -- + * YScrollByLines -- * * This procedure is called to scroll a text widget up or down * by a given number of lines. @@ -3505,9 +4783,9 @@ TkTextXviewCmd(textPtr, interp, objc, objv) */ static void -ScrollByLines(textPtr, offset) +YScrollByLines(textPtr, offset) TkText *textPtr; /* Widget to scroll. */ - int offset; /* Amount by which to scroll, in *screen* + int offset; /* Amount by which to scroll, in display * lines. Positive means that information * later in text becomes visible, negative * means that information earlier in the @@ -3556,7 +4834,7 @@ ScrollByLines(textPtr, offset) * for the next display line to lay out. */ - FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0); + FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE); if (offset >= 0) { goto scheduleUpdate; } @@ -3565,10 +4843,12 @@ ScrollByLines(textPtr, offset) /* * Ran off the beginning of the text. Return the first character - * in the text. + * in the text, and make sure we haven't left anything + * overlapping the top window border. */ TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); + dInfoPtr->newTopPixelOffset = 0; } else { /* * Scrolling down, to show later information in the text. @@ -3582,7 +4862,7 @@ ScrollByLines(textPtr, offset) if (dlPtr->length == 0 && dlPtr->height == 0) offset++; dlPtr->nextPtr = NULL; TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); + FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE); if (new.linePtr == lastLinePtr) { break; } @@ -3625,14 +4905,11 @@ TkTextYviewCmd(textPtr, interp, objc, objv) * objv[1] is "yview". */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - int pickPlace, lineNum, type, bytesInLine; - Tk_FontMetrics fm; + int pickPlace, type; int pixels, count; size_t switchLength; double fraction; - TkTextIndex index, new; - TkTextLine *lastLinePtr; - DLine *dlPtr; + TkTextIndex index; if (dInfoPtr->flags & DINFO_OUT_OF_DATE) { UpdateDisplayInfo(textPtr); @@ -3651,18 +4928,17 @@ TkTextYviewCmd(textPtr, interp, objc, objv) if (Tcl_GetString(objv[2])[0] == '-') { switchLength = strlen(Tcl_GetString(objv[2])); if ((switchLength >= 2) - && (strncmp(Tcl_GetString(objv[2]), "-pickplace", switchLength) == 0)) { + && (strncmp(Tcl_GetString(objv[2]), + "-pickplace", switchLength) == 0)) { pickPlace = 1; if (objc != 4) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - Tcl_GetString(objv[0]), - " yview -pickplace lineNum|index\"", - (char *) NULL); + Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index"); return TCL_ERROR; } } } if ((objc == 3) || pickPlace) { + int lineNum; if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) { TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); TkTextSetYView(textPtr, &index, 0); @@ -3678,7 +4954,7 @@ TkTextYviewCmd(textPtr, interp, objc, objv) &index) != TCL_OK) { return TCL_ERROR; } - TkTextSetYView(textPtr, &index, pickPlace); + TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0)); return TCL_OK; } @@ -3686,79 +4962,67 @@ TkTextYviewCmd(textPtr, interp, objc, objv) * New syntax: dispatch based on objv[2]. */ - type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count); + type = TextGetScrollInfoObj(interp, textPtr, objc, objv, + &fraction, &count); switch (type) { - case TK_SCROLL_ERROR: + case TKTEXT_SCROLL_ERROR: return TCL_ERROR; - case TK_SCROLL_MOVETO: + case TKTEXT_SCROLL_MOVETO: if (fraction > 1.0) { fraction = 1.0; } if (fraction < 0) { fraction = 0; } - fraction *= TkBTreeNumLines(textPtr->tree); - lineNum = (int) fraction; - TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); - bytesInLine = TkBTreeBytesInLine(index.linePtr); - index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5); - if (index.byteIndex >= bytesInLine) { - TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index); - } - TkTextSetYView(textPtr, &index, 0); + fraction *= (TkBTreeNumPixels(textPtr->tree)-1); + /* + * This function returns the number of pixels by which the + * given line should overlap the top of the visible screen. + * + * This is then used to provide smooth scrolling. + */ + pixels = TkTextMakePixelIndex(textPtr, (int) (0.5 + fraction), + &index); + TkTextSetYView(textPtr, &index, pixels); break; - case TK_SCROLL_PAGES: + case TKTEXT_SCROLL_PAGES: { /* * Scroll up or down by screenfuls. Actually, use the * window height minus two lines, so that there's some * overlap between adjacent pages. */ - - Tk_GetFontMetrics(textPtr->tkfont, &fm); - if (count < 0) { - pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count) - + fm.linespace; - MeasureUp(textPtr, &textPtr->topIndex, pixels, &new); - if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) { - /* - * A page of scrolling ended up being less than one line. - * Scroll one line anyway. + int height = dInfoPtr->maxY - dInfoPtr->y; + if (textPtr->charHeight * 4 >= height) { + /* + * A single line is more than a quarter of the + * display. We choose to scroll by 3/4 of the + * height instead. + */ + pixels = 3*height/4; + if (pixels < textPtr->charHeight) { + /* + * But, if 3/4 of the height is actually less than a + * single typical character height, then scroll by + * the minimum of the linespace or the total height. */ - - count = -1; - goto scrollByLines; + if (textPtr->charHeight < height) { + pixels = textPtr->charHeight; + } else { + pixels = height; + } } - textPtr->topIndex = new; + pixels *= count; } else { - /* - * Scrolling down by pages. Layout lines starting at the - * top index and count through the desired vertical distance. - */ - - pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count; - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); - do { - dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); - dlPtr->nextPtr = NULL; - TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, - &new); - pixels -= dlPtr->height; - FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0); - if (new.linePtr == lastLinePtr) { - break; - } - textPtr->topIndex = new; - } while (pixels > 0); + pixels = (height - 2*textPtr->charHeight)*count; } - if (!(dInfoPtr->flags & REDRAW_PENDING)) { - Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr); - } - dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED; + YScrollByPixels(textPtr, pixels); break; - case TK_SCROLL_UNITS: - scrollByLines: - ScrollByLines(textPtr, count); + } + case TKTEXT_SCROLL_PIXELS: + YScrollByPixels(textPtr, count); + break; + case TKTEXT_SCROLL_UNITS: + YScrollByLines(textPtr, count); break; } return TCL_OK; @@ -3794,13 +5058,13 @@ TkTextScanCmd(textPtr, interp, objc, objv) TextDInfo *dInfoPtr = textPtr->dInfoPtr; TkTextIndex index; int c, x, y, totalScroll, newByte, maxByte, gain=10; - Tk_FontMetrics fm; size_t length; if ((objc != 5) && (objc != 6)) { Tcl_AppendResult(interp, "wrong # args: should be \"", Tcl_GetString(objv[0]), " scan mark x y\" or \"", - Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", (char *) NULL); + Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", + (char *) NULL); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) { @@ -3809,8 +5073,9 @@ TkTextScanCmd(textPtr, interp, objc, objv) if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) { return TCL_ERROR; } - if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) - return TCL_ERROR; + if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) { + return TCL_ERROR; + } c = Tcl_GetString(objv[2])[0]; length = strlen(Tcl_GetString(objv[2])); if ((c == 'd') && (strncmp(Tcl_GetString(objv[2]), "dragto", length) == 0)) { @@ -3839,24 +5104,24 @@ TkTextScanCmd(textPtr, interp, objc, objv) dInfoPtr->scanMarkIndex = maxByte; dInfoPtr->scanMarkX = x; } - dInfoPtr->newByteOffset = newByte; + dInfoPtr->newXByteOffset = newByte; - Tk_GetFontMetrics(textPtr->tkfont, &fm); - totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace; - if (totalScroll != dInfoPtr->scanTotalScroll) { + totalScroll = gain*(dInfoPtr->scanMarkY - y); + if (totalScroll != dInfoPtr->scanTotalYScroll) { index = textPtr->topIndex; - ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll); - dInfoPtr->scanTotalScroll = totalScroll; + YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll); + dInfoPtr->scanTotalYScroll = totalScroll; if ((index.linePtr == textPtr->topIndex.linePtr) && (index.byteIndex == textPtr->topIndex.byteIndex)) { - dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanTotalYScroll = 0; dInfoPtr->scanMarkY = y; } } - } else if ((c == 'm') && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) { - dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset; + } else if ((c == 'm') + && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) { + dInfoPtr->scanMarkIndex = dInfoPtr->newXByteOffset; dInfoPtr->scanMarkX = x; - dInfoPtr->scanTotalScroll = 0; + dInfoPtr->scanTotalYScroll = 0; dInfoPtr->scanMarkY = y; } else { Tcl_AppendResult(interp, "bad scan option \"", Tcl_GetString(objv[2]), @@ -3906,12 +5171,12 @@ GetXView(interp, textPtr, report) * scrollbar if it has changed. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - char buffer[TCL_DOUBLE_SPACE * 2 + 1]; double first, last; int code; - + Tcl_Obj *listObj; + if (dInfoPtr->maxLength > 0) { - first = ((double) dInfoPtr->curPixelOffset) + first = ((double) dInfoPtr->curXPixelOffset) / dInfoPtr->maxLength; last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x)) / dInfoPtr->maxLength; @@ -3923,8 +5188,10 @@ GetXView(interp, textPtr, report) last = 1.0; } if (!report) { - sprintf(buffer, "%g %g", first, last); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); + listObj = Tcl_NewListObj(0,NULL); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObj); return; } if (FP_EQUAL_SCALE(first, dInfoPtr->xScrollFirst, dInfoPtr->maxLength) && @@ -3933,19 +5200,67 @@ GetXView(interp, textPtr, report) } dInfoPtr->xScrollFirst = first; dInfoPtr->xScrollLast = last; - sprintf(buffer, " %g %g", first, last); - code = Tcl_VarEval(interp, textPtr->xScrollCmd, - buffer, (char *) NULL); - if (code != TCL_OK) { - Tcl_AddErrorInfo(interp, + if (textPtr->xScrollCmd != NULL) { + listObj = Tcl_NewStringObj(textPtr->xScrollCmd, -1); + code = Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + if (code == TCL_OK) { + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + code = Tcl_EvalObjEx(interp, listObj, + TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL); + } + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, "\n (horizontal scrolling command executed by text)"); - Tcl_BackgroundError(interp); + Tcl_BackgroundError(interp); + } } } /* *---------------------------------------------------------------------- * + * GetPixelCount -- + * + * How many pixels are there between the absolute top of the + * widget and the top of the given DLine. + * + * Results: + * The number of pixels. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetPixelCount(textPtr, dlPtr) + TkText *textPtr; /* Information about text widget. */ + DLine *dlPtr; /* Information about the layout + * of a given index */ +{ + TkTextLine *linePtr = dlPtr->index.linePtr; + /* + * Get the pixel count of one pixel beyond the + * botton of the given line. + */ + int count = TkBTreePixels(linePtr) + linePtr->pixelHeight; + + /* + * Now we have to subtract off the distance between the top of this + * dlPtr and the next logical line. + */ + do { + count -= dlPtr->height; + dlPtr = dlPtr->nextPtr; + } while (dlPtr != NULL && (dlPtr->index.linePtr == linePtr)); + + return count; +} + +/* + *---------------------------------------------------------------------- + * * GetYView -- * * This procedure computes the fractions that indicate what's @@ -3967,7 +5282,7 @@ GetXView(interp, textPtr, report) * *---------------------------------------------------------------------- */ - + static void GetYView(interp, textPtr, report) Tcl_Interp *interp; /* If "report" is FALSE, string @@ -3978,53 +5293,153 @@ GetYView(interp, textPtr, report) * scrollbar if it has changed. */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - char buffer[TCL_DOUBLE_SPACE * 2 + 1]; double first, last; DLine *dlPtr; - int totalLines, code, count; - + int totalPixels, code, count; + Tcl_Obj *listObj; + dlPtr = dInfoPtr->dLinePtr; - totalLines = TkBTreeNumLines(textPtr->tree); - first = (double) TkBTreeLineIndex(dlPtr->index.linePtr) - + (double) dlPtr->index.byteIndex - / TkBTreeBytesInLine(dlPtr->index.linePtr); - first /= totalLines; + + if (dlPtr == NULL) { + return; + } + + totalPixels = TkBTreeNumPixels(textPtr->tree); + + /* + * Get the pixel count for the first display visible pixel of the + * first visible line. If the first visible line is only partially + * visible, then we use 'topPixelOffset' to get the difference. + */ + count = GetPixelCount(textPtr, dlPtr); + first = ((double) (count + dInfoPtr->topPixelOffset))/((double)totalPixels); + + /* + * Add on the total number of visible pixels to get the count to + * the last visible pixel. + */ while (1) { - if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { + int extra; + count += dlPtr->height; + + extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY; + if (extra > 0) { /* - * The last line is only partially visible, so don't - * count its characters in what's visible. + * This much of the last line is not visible, so don't + * count these pixels. Since we've reached the bottom + * of the window, we break out of the loop. */ - count = 0; + count -= extra; break; } if (dlPtr->nextPtr == NULL) { - count = dlPtr->byteCount; break; } dlPtr = dlPtr->nextPtr; } - last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr)) - + ((double) (dlPtr->index.byteIndex + count)) - / (TkBTreeBytesInLine(dlPtr->index.linePtr)); - last /= totalLines; + + if (count > totalPixels) { + /* + * It can be possible, if we do not update each line's + * pixelHeight cache when we lay out individual DLines that + * the count generated here is more up-to-date than that + * maintained by the BTree. In such a case, the best we can + * do here is to fix up 'count' and continue, which might + * result in small, temporary perturbations to the size of + * the scrollbar. This is basically harmless, but in a + * perfect world we would not have this problem. + * + * For debugging purposes, if anyone wishes to improve the text + * widget further, the following 'panic' can be activated. In + * principle it should be possible to ensure the BTree is always + * at least as up to date as the display, so in the future we + * might be able to leave the 'panic' in permanently when we + * believe we have resolved the cache synchronisation issue. + * + * However, to achieve that goal would, I think, require a + * fairly substantial refactorisation of the code in this + * file so that there is much more obvious and explicit + * coordination between calls to LayoutDLine and updating + * of each TkTextLine's pixelHeight. The complicated bit + * is that LayoutDLine deals with individual display lines, + * but pixelHeight is for a logical line. + */ +#if 0 + char buffer[200]; + + sprintf(buffer, + "Counted more pixels (%d) than expected (%d) total pixels in text widget scroll bar calculation.", + count, totalPixels); + panic(buffer); +#endif + count = totalPixels; + } + + last = ((double) (count))/((double)totalPixels); + if (!report) { - sprintf(buffer, "%g %g", first, last); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); + listObj = Tcl_NewListObj(0,NULL); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObj); return; } - if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalLines) && - FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalLines)) { + if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) && + FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) { return; } dInfoPtr->yScrollFirst = first; dInfoPtr->yScrollLast = last; - sprintf(buffer, " %g %g", first, last); - code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL); - if (code != TCL_OK) { - Tcl_AddErrorInfo(interp, - "\n (vertical scrolling command executed by text)"); - Tcl_BackgroundError(interp); + if (textPtr->yScrollCmd != NULL) { + listObj = Tcl_NewStringObj(textPtr->yScrollCmd, -1); + code = Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewDoubleObj(first)); + if (code == TCL_OK) { + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last)); + code = Tcl_EvalObjEx(interp, listObj, + TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL); + } + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (vertical scrolling command executed by text)"); + Tcl_BackgroundError(interp); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateYScrollbar -- + * + * This procedure is called to update the vertical scrollbar + * asychronously as the pixel height calculations progress for + * lines in the widget. + * + * Results: + * None. + * + * Side effects: + * See 'GetYView'. In particular the scrollbar position and size + * may be changed. + * + *---------------------------------------------------------------------- + */ + +void +TkTextUpdateYScrollbar(clientData) + ClientData clientData; /* Information about widget. */ +{ + register TkText *textPtr = (TkText *) clientData; + + textPtr->dInfoPtr->scrollbarTimer = NULL; + + if (!(textPtr->flags & DESTROYED)) { + GetYView(textPtr->interp, textPtr, 1); + } + + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); } } @@ -4128,8 +5543,7 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) * index of the character nearest to (x,y). */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; - register DLine *dlPtr, *validdlPtr; - register TkTextDispChunk *chunkPtr; + register DLine *dlPtr, *validDlPtr; /* * Make sure that all of the layout information about what's @@ -4161,9 +5575,10 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) * Find the display line containing the desired y-coordinate. */ - for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height); - dlPtr = dlPtr->nextPtr) { - if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr; + for (dlPtr = validDlPtr = dInfoPtr->dLinePtr; + y >= (dlPtr->y + dlPtr->height); + dlPtr = dlPtr->nextPtr) { + if (dlPtr->chunkPtr !=NULL) validDlPtr = dlPtr; if (dlPtr->nextPtr == NULL) { /* * Y-coordinate is off the bottom of the displayed text. @@ -4174,8 +5589,43 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) break; } } - if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr; + if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr; + + DlineIndexOfX(textPtr, dlPtr, x, indexPtr); +} + +/* + *---------------------------------------------------------------------- + * + * DlineIndexOfX -- + * + * Given an x coordinate in a display line, find the index of + * the character closest to that location. + * + * This is effectively the opposite of DlineXOfIndex. + * + * Results: + * The index at *indexPtr is modified to refer to the character + * on the display line that is closest to x. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +static void +DlineIndexOfX(textPtr, dlPtr, x, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + DLine *dlPtr; /* Display information for this + * display line. */ + int x; /* Pixel x coordinate of point in widget's + * window. */ + TkTextIndex *indexPtr; /* This index gets filled in with the + * index of the character nearest to x. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register TkTextDispChunk *chunkPtr; /* * Scan through the line's chunks to find the one that contains @@ -4185,13 +5635,13 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) */ *indexPtr = dlPtr->index; - x = x - dInfoPtr->x + dInfoPtr->curPixelOffset; + x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset; for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width); indexPtr->byteIndex += chunkPtr->numBytes, chunkPtr = chunkPtr->nextPtr) { if (chunkPtr->nextPtr == NULL) { indexPtr->byteIndex += chunkPtr->numBytes; - TkTextIndexBackChars(indexPtr, 1, indexPtr); + TkTextIndexBackChars(indexPtr, 1, indexPtr, COUNT_INDICES); return; } } @@ -4209,6 +5659,110 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) /* *---------------------------------------------------------------------- * + * TkTextIndexOfX -- + * + * Given a logical x coordinate (i.e. distance in pixels from the + * beginning of the display line, not taking into account any + * information about the window, scrolling etc.) on the display + * line starting with the given index, adjust that index to refer to + * the object under the x coordinate. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkTextIndexOfX(textPtr, x, indexPtr) + TkText *textPtr; /* Widget record for text widget. */ + int x; /* The x coordinate for which we want + * the index */ + TkTextIndex *indexPtr; /* Index of display line start, which + * will be adjusted to the index under the + * given x coordinate. */ +{ + DLine *dlPtr = LayoutDLine(textPtr, indexPtr); + DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x + - textPtr->dInfoPtr->curXPixelOffset, indexPtr); + FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); +} + +/* + *---------------------------------------------------------------------- + * + * DlineXOfIndex -- + * + * Given a relative byte index on a given display line (i.e. the + * number of byte indices from the beginning of the given display + * line), find the x coordinate of that index within the abstract + * display line, without adjusting for the x-scroll state of the + * line. + * + * This is effectively the opposite of DlineIndexOfX. + * + * NB. The 'byteIndex' is relative to the display line, NOT the + * logical line. + * + * Results: + * The x coordinate. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DlineXOfIndex(textPtr, dlPtr, byteIndex) + TkText *textPtr; /* Widget record for text widget. */ + DLine *dlPtr; /* Display information for this + * display line. */ + int byteIndex; /* The byte index for which we want the + * coordinate. */ +{ + TextDInfo *dInfoPtr = textPtr->dInfoPtr; + register TkTextDispChunk *chunkPtr; + TkTextIndex index; + int x; + + if (byteIndex == 0) return 0; + + /* + * Scan through the line's chunks to find the one that contains + * the desired byte index. + */ + + index = dlPtr->index; + chunkPtr = dlPtr->chunkPtr; + while (byteIndex > 0) { + if (byteIndex < chunkPtr->numBytes) { + int y, width, height; + (*chunkPtr->bboxProc)(chunkPtr, byteIndex, + dlPtr->y + dlPtr->spaceAbove, + dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, + dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, + &height); + break; + } else { + byteIndex -= chunkPtr->numBytes; + } + if (chunkPtr->nextPtr == NULL || byteIndex == 0) { + x = chunkPtr->x + chunkPtr->width; + break; + } + chunkPtr = chunkPtr->nextPtr; + } + + return x; +} + +/* + *---------------------------------------------------------------------- + * * TkTextCharBbox -- * * Given an index, find the bounding box of the screen area @@ -4228,13 +5782,18 @@ TkTextPixelIndex(textPtr, x, y, indexPtr) */ int -TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) +TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, charWidthPtr) TkText *textPtr; /* Widget record for text widget. */ CONST TkTextIndex *indexPtr;/* Index of character whose bounding * box is desired. */ int *xPtr, *yPtr; /* Filled with character's upper-left * coordinate. */ int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */ + int *charWidthPtr; /* If the 'character' isn't really a + * character (e.g. end of a line) and + * therefore takes up a very large + * width, this is used to return a + * smaller width */ { TextDInfo *dInfoPtr = textPtr->dInfoPtr; DLine *dlPtr; @@ -4286,17 +5845,24 @@ TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr) dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, heightPtr); - *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset; + *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset; if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) { /* * Last character in display line. Give it all the space up to * the line. */ + if (charWidthPtr != NULL) { + *charWidthPtr = dInfoPtr->maxX - *xPtr; + } if (*xPtr > dInfoPtr->maxX) { *xPtr = dInfoPtr->maxX; } *widthPtr = dInfoPtr->maxX - *xPtr; + } else { + if (charWidthPtr != NULL) { + *charWidthPtr = *widthPtr; + } } if ((*xPtr + *widthPtr) <= dInfoPtr->x) { return -1; @@ -4369,7 +5935,7 @@ TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) } dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0); - *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx; + *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx; *widthPtr = dlPtr->length - dlx; *yPtr = dlPtr->y; if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) { @@ -4454,8 +6020,9 @@ TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, * many characters. */ int noCharsYet; /* Non-zero means no characters have been * assigned to this display line yet. */ - TkWrapMode wrapMode; /* How to handle line wrapping: TEXT_WRAPMODE_CHAR, - * TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */ + TkWrapMode wrapMode; /* How to handle line wrapping: + * TEXT_WRAPMODE_CHAR, * TEXT_WRAPMODE_NONE, + * or TEXT_WRAPMODE_WORD. */ register TkTextDispChunk *chunkPtr; /* Structure to fill in with information * about this chunk. The x field has already @@ -4643,7 +6210,8 @@ CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) * Draw the text, underline, and overstrike for this chunk. */ - if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) { + if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) + && (stylePtr->fgGC != None)) { int numBytes = ciPtr->numBytes - offsetBytes; char *string = ciPtr->chars + offsetBytes; @@ -4759,7 +6327,7 @@ static void CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, widthPtr, heightPtr) TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ - int byteIndex; /* Byte offset of desired character + int byteIndex; /* Byte offset of desired character * within the chunk. */ int y; /* Topmost pixel in area allocated * for this line. */ @@ -5235,3 +6803,102 @@ MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr) *nextXPtr = curX; return start - source; } + +/* + *---------------------------------------------------------------------- + * + * TextGetScrollInfoObj -- + * + * This procedure is invoked to parse "yview" scrolling commands for + * text widgets using the new scrolling command syntax ("moveto" or + * "scroll" options). It extends the public Tk_GetScrollInfoObj + * function with the addition of "pixels" as a valid unit alongside + * "pages" and "units". It is a shame the core API isn't more + * flexible in this regard. + * + * Results: + * The return value is either TKTEXT_SCROLL_MOVETO, + * TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or + * TKTEXT_SCROLL_ERROR. This indicates whether the command was + * successfully parsed and what form the command took. If + * TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with the desired + * position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or + * TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of + * pages/pixels/lines to move (may be negative); if + * TKTEXT_SCROLL_ERROR, the interp's result contains an error + * message. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TextGetScrollInfoObj(interp, textPtr, objc, objv, dblPtr, intPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + TkText *textPtr; /* Information about the text + * widget. */ + int objc; /* # arguments for command. */ + Tcl_Obj *CONST objv[]; /* Arguments for command. */ + double *dblPtr; /* Filled in with argument "moveto" + * option, if any. */ + int *intPtr; /* Filled in with number of pages + * or lines or pixels to scroll, + * if any. */ +{ + char c; + size_t length; + CONST char *arg2; + + arg2 = Tcl_GetStringFromObj(objv[2], &length); + c = arg2[0]; + if ((c == 'm') && (strncmp(arg2, "moveto", length) == 0)) { + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "moveto fraction"); + return TKTEXT_SCROLL_ERROR; + } + if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_MOVETO; + } else if ((c == 's') && (strncmp(arg2, "scroll", length) == 0)) { + CONST char *arg4; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, + "scroll number units|pages|pixels"); + return TKTEXT_SCROLL_ERROR; + } + arg4 = Tcl_GetStringFromObj(objv[4], &length); + c = arg4[0]; + if ((c == 'p') && (length == 1)) { + Tcl_AppendResult(interp, "ambiguous argument \"", arg4, + "\": must be units, pages or pixels", (char *) NULL); + return TKTEXT_SCROLL_ERROR; + } else if ((c == 'p') && (strncmp(arg4, "pages", length) == 0)) { + if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_PAGES; + } else if ((c == 'p') && (strncmp(arg4, "pixels", length) == 0)) { + if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3], + intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_PIXELS; + } else if ((c == 'u') && (strncmp(arg4, "units", length) == 0)) { + if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) { + return TKTEXT_SCROLL_ERROR; + } + return TKTEXT_SCROLL_UNITS; + } else { + Tcl_AppendResult(interp, "bad argument \"", arg4, + "\": must be units, pages or pixels", (char *) NULL); + return TKTEXT_SCROLL_ERROR; + } + } + Tcl_AppendResult(interp, "unknown option \"", arg2, + "\": must be moveto or scroll", (char *) NULL); + return TKTEXT_SCROLL_ERROR; +} diff --git a/generic/tkTextImage.c b/generic/tkTextImage.c index ef3dd93..35e4138 100644 --- a/generic/tkTextImage.c +++ b/generic/tkTextImage.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextImage.c,v 1.8 2003/09/29 23:15:20 dkf Exp $ + * RCS: @(#) $Id: tkTextImage.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $ */ #include "tk.h" @@ -18,15 +18,6 @@ #include "tkPort.h" /* - * Definitions for alignment values: - */ - -#define ALIGN_BOTTOM 0 -#define ALIGN_CENTER 1 -#define ALIGN_TOP 2 -#define ALIGN_BASELINE 3 - -/* * Macro that determines the size of an embedded image segment: */ @@ -37,12 +28,6 @@ * Prototypes for procedures defined in this file: */ -static int AlignParseProc _ANSI_ARGS_((ClientData clientData, - Tcl_Interp *interp, Tk_Window tkwin, - CONST char *value, char *widgRec, int offset)); -static char * AlignPrintProc _ANSI_ARGS_((ClientData clientData, - Tk_Window tkwin, char *widgRec, int offset, - Tcl_FreeProc **freeProcPtr)); static TkTextSegment * EmbImageCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static void EmbImageCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, @@ -84,30 +69,40 @@ static Tk_SegType tkTextEmbImageType = { }; /* + * Definitions for alignment values: + */ + +static char *alignStrings[] = { + "baseline", "bottom", "center", "top", (char *) NULL +}; + +typedef enum { + ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER, ALIGN_TOP +} alignMode; + +/* * Information used for parsing image configuration options: */ -static Tk_CustomOption alignOption = {AlignParseProc, AlignPrintProc, - (ClientData) NULL}; - -static Tk_ConfigSpec configSpecs[] = { - {TK_CONFIG_CUSTOM, "-align", (char *) NULL, (char *) NULL, - "center", 0, TK_CONFIG_DONT_SET_DEFAULT, &alignOption}, - {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL, - "0", Tk_Offset(TkTextEmbImage, padX), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL, - "0", Tk_Offset(TkTextEmbImage, padY), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL, - (char *) NULL, Tk_Offset(TkTextEmbImage, imageString), - TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK}, - {TK_CONFIG_STRING, "-name", (char *) NULL, (char *) NULL, - (char *) NULL, Tk_Offset(TkTextEmbImage, imageName), - TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK}, - {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, - (char *) NULL, 0, 0} +static Tk_OptionSpec optionSpecs[] = { + {TK_OPTION_STRING_TABLE, "-align", (char *) NULL, (char *) NULL, + "center", -1, Tk_Offset(TkTextEmbImage, align), + 0, (ClientData) alignStrings, 0}, + {TK_OPTION_PIXELS, "-padx", (char *) NULL, (char *) NULL, + "0", -1, Tk_Offset(TkTextEmbImage, padX), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-pady", (char *) NULL, (char *) NULL, + "0", -1, Tk_Offset(TkTextEmbImage, padY), + 0, 0, 0}, + {TK_OPTION_STRING, "-image", (char *) NULL, (char *) NULL, + (char *) NULL, -1, Tk_Offset(TkTextEmbImage, imageString), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_STRING, "-name", (char *) NULL, (char *) NULL, + (char *) NULL, -1, Tk_Offset(TkTextEmbImage, imageName), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_END} }; + /* *-------------------------------------------------------------- @@ -155,7 +150,8 @@ TkTextImageCmd(textPtr, interp, objc, objv) return TCL_ERROR; } switch ((enum opts) idx) { - case CMD_CGET: + case CMD_CGET: { + Tcl_Obj *objPtr; if (objc != 5) { Tcl_WrongNumArgs(interp, 3, objv, "index option"); return TCL_ERROR; @@ -169,9 +165,16 @@ TkTextImageCmd(textPtr, interp, objc, objv) Tcl_GetString(objv[3]), "\"", (char *) NULL); return TCL_ERROR; } - return Tk_ConfigureValue(interp, textPtr->tkwin, configSpecs, - (char *) &eiPtr->body.ei, Tcl_GetString(objv[4]), 0); - case CMD_CONF: + objPtr = Tk_GetOptionValue(interp, (char *) &eiPtr->body.ei, + eiPtr->body.ei.optionTable, objv[4], textPtr->tkwin); + if (objPtr == NULL) { + return TCL_ERROR; + } else { + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; + } + } + case CMD_CONF: { if (objc < 4) { Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?"); return TCL_ERROR; @@ -185,16 +188,29 @@ TkTextImageCmd(textPtr, interp, objc, objv) Tcl_GetString(objv[3]), "\"", (char *) NULL); return TCL_ERROR; } - if (objc == 4) { - return Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs, - (char *) &eiPtr->body.ei, (char *) NULL, 0); - } else if (objc == 5) { - return Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs, - (char *) &eiPtr->body.ei, Tcl_GetString(objv[4]), 0); + if (objc <= 5) { + Tcl_Obj* objPtr = Tk_GetOptionInfo(interp, (char *) &eiPtr->body.ei, + eiPtr->body.ei.optionTable, + (objc == 5) ? objv[4] : (Tcl_Obj *) NULL, + textPtr->tkwin); + if (objPtr == NULL) { + return TCL_ERROR; + } else { + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; + } } else { TkTextChanged(textPtr, &index, &index); + /* + * It's probably not true that all window configuration + * can change the line height, so we could be more + * efficient here and only call this when necessary. + */ + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); return EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4); } + } case CMD_CREATE: { int lineIndex; @@ -237,6 +253,7 @@ TkTextImageCmd(textPtr, interp, objc, objv) eiPtr->body.ei.align = ALIGN_CENTER; eiPtr->body.ei.padX = eiPtr->body.ei.padY = 0; eiPtr->body.ei.chunkCount = 0; + eiPtr->body.ei.optionTable = Tk_CreateOptionTable(interp, optionSpecs); /* * Link the segment into the text widget, then configure it (delete @@ -248,10 +265,12 @@ TkTextImageCmd(textPtr, interp, objc, objv) if (EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4) != TCL_OK) { TkTextIndex index2; - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); TkBTreeDeleteChars(&index, &index2); return TCL_ERROR; } + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); return TCL_OK; } case CMD_NAMES: { @@ -312,16 +331,12 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) int count = 0; /* The counter for picking a unique name */ int conflict = 0; /* True if we have a name conflict */ unsigned int len; /* length of image name */ - CONST char **argv; - argv = TkGetStringsFromObjs(objc, objv); - if (Tk_ConfigureWidget(textPtr->interp, textPtr->tkwin, configSpecs, - objc, argv, (char *) &eiPtr->body.ei,TK_CONFIG_ARGV_ONLY) - != TCL_OK) { - if (argv) ckfree((char *) argv); + if (Tk_SetOptions(textPtr->interp, (char*)&eiPtr->body.ei, + eiPtr->body.ei.optionTable, + objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) { return TCL_ERROR; } - if (argv) ckfree((char *) argv); /* * Create the image. Save the old image around and don't free it @@ -331,8 +346,9 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) */ if (eiPtr->body.ei.imageString != NULL) { - image = Tk_GetImage(textPtr->interp, textPtr->tkwin, eiPtr->body.ei.imageString, - EmbImageProc, (ClientData) eiPtr); + image = Tk_GetImage(textPtr->interp, textPtr->tkwin, + eiPtr->body.ei.imageString, EmbImageProc, + (ClientData) eiPtr); if (image == NULL) { return TCL_ERROR; } @@ -350,8 +366,8 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) /* * Find a unique name for this image. Use imageName (or imageString) - * if available, otherwise tack on a #nn and use it. If a name is already - * associated with this image, delete the name. + * if available, otherwise tack on a #nn and use it. If a name is + * already associated with this image, delete the name. */ name = eiPtr->body.ei.imageName; @@ -403,99 +419,6 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) /* *-------------------------------------------------------------- * - * AlignParseProc -- - * - * This procedure is invoked by Tk_ConfigureWidget during - * option processing to handle "-align" options for embedded - * images. - * - * Results: - * A standard Tcl return value. - * - * Side effects: - * The alignment for the embedded image may change. - * - *-------------------------------------------------------------- - */ - - /* ARGSUSED */ -static int -AlignParseProc(clientData, interp, tkwin, value, widgRec, offset) - ClientData clientData; /* Not used.*/ - Tcl_Interp *interp; /* Used for reporting errors. */ - Tk_Window tkwin; /* Window for text widget. */ - CONST char *value; /* Value of option. */ - char *widgRec; /* Pointer to TkTextEmbWindow - * structure. */ - int offset; /* Offset into item (ignored). */ -{ - register TkTextEmbImage *embPtr = (TkTextEmbImage *) widgRec; - - if (strcmp(value, "baseline") == 0) { - embPtr->align = ALIGN_BASELINE; - } else if (strcmp(value, "bottom") == 0) { - embPtr->align = ALIGN_BOTTOM; - } else if (strcmp(value, "center") == 0) { - embPtr->align = ALIGN_CENTER; - } else if (strcmp(value, "top") == 0) { - embPtr->align = ALIGN_TOP; - } else { - Tcl_AppendResult(interp, "bad alignment \"", value, - "\": must be baseline, bottom, center, or top", - (char *) NULL); - return TCL_ERROR; - } - return TCL_OK; -} - -/* - *-------------------------------------------------------------- - * - * AlignPrintProc -- - * - * This procedure is invoked by the Tk configuration code - * to produce a printable string for the "-align" configuration - * option for embedded images. - * - * Results: - * The return value is a string describing the embedded - * images's current alignment. - * - * Side effects: - * None. - * - *-------------------------------------------------------------- - */ - - /* ARGSUSED */ -static char * -AlignPrintProc(clientData, tkwin, widgRec, offset, freeProcPtr) - ClientData clientData; /* Ignored. */ - Tk_Window tkwin; /* Window for text widget. */ - char *widgRec; /* Pointer to TkTextEmbImage - * structure. */ - int offset; /* Ignored. */ - Tcl_FreeProc **freeProcPtr; /* Pointer to variable to fill in with - * information about how to reclaim - * storage for return string. */ -{ - switch (((TkTextEmbImage *) widgRec)->align) { - case ALIGN_BASELINE: - return "baseline"; - case ALIGN_BOTTOM: - return "bottom"; - case ALIGN_CENTER: - return "center"; - case ALIGN_TOP: - return "top"; - default: - return "??"; - } -} - -/* - *-------------------------------------------------------------- - * * EmbImageDeleteProc -- * * This procedure is invoked by the text B-tree code whenever @@ -536,11 +459,8 @@ EmbImageDeleteProc(eiPtr, linePtr, treeGone) } Tk_FreeImage(eiPtr->body.ei.image); } - Tk_FreeOptions(configSpecs, (char *) &eiPtr->body.ei, - eiPtr->body.ei.textPtr->display, 0); - if (eiPtr->body.ei.name != NULL) { - ckfree(eiPtr->body.ei.name); - } + Tk_FreeConfigOptions((char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable, + eiPtr->body.ei.textPtr->tkwin); ckfree((char *) eiPtr); return 0; } @@ -896,4 +816,11 @@ EmbImageProc(clientData, x, y, width, height, imgWidth, imgHeight) index.linePtr = eiPtr->body.ei.linePtr; index.byteIndex = TkTextSegToOffset(eiPtr, eiPtr->body.ei.linePtr); TkTextChanged(eiPtr->body.ei.textPtr, &index, &index); + /* + * It's probably not true that all image changes + * can change the line height, so we could be more + * efficient here and only call this when necessary. + */ + TkTextInvalidateLineMetrics(eiPtr->body.ei.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c index 06396db..66caa5a 100644 --- a/generic/tkTextIndex.c +++ b/generic/tkTextIndex.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextIndex.c,v 1.8 2003/10/10 15:56:22 dkf Exp $ + * RCS: @(#) $Id: tkTextIndex.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $ */ #include "default.h" @@ -25,18 +25,28 @@ #define LAST_CHAR 1000000 /* + * Modifiers for index parsing: 'display', 'any' or nothing. + */ +#define TKINDEX_NONE 0 +#define TKINDEX_DISPLAY 1 +#define TKINDEX_ANY 2 + +/* * Forward declarations for procedures defined later in this file: */ -static CONST char * ForwBack _ANSI_ARGS_((CONST char *string, - TkTextIndex *indexPtr)); -static CONST char * StartEnd _ANSI_ARGS_((CONST char *string, - TkTextIndex *indexPtr)); +static CONST char * ForwBack _ANSI_ARGS_((TkText *textPtr, + CONST char *string, TkTextIndex *indexPtr)); +static CONST char * StartEnd _ANSI_ARGS_((TkText *textPtr, + CONST char *string, TkTextIndex *indexPtr)); static int GetIndex _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, CONST char *string, TkTextIndex *indexPtr, int *canCachePtr)); - +/* + * The "textindex" Tcl_Obj definition: + */ + static void DupTextIndexInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr, Tcl_Obj *copyPtr)); static void FreeTextIndexInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr)); @@ -96,7 +106,6 @@ DupTextIndexInternalRep(srcPtr, copyPtr) SET_TEXTINDEX(copyPtr, dupIndexPtr); SET_INDEXEPOCH(copyPtr, epoch); } - /* * This will not be called except by TkTextNewIndexObj below. * This is because if a TkTextIndex is no longer valid, it is @@ -117,7 +126,6 @@ UpdateStringOfTextIndex(objPtr) strcpy(objPtr->bytes, buffer); objPtr->length = len; } - static int SetTextIndexFromAny(interp, objPtr) Tcl_Interp *interp; /* Used for error reporting if not NULL. */ @@ -128,7 +136,7 @@ SetTextIndexFromAny(interp, objPtr) -1); return TCL_ERROR; } - + /* *--------------------------------------------------------------------------- * @@ -176,7 +184,7 @@ MakeObjIndex(textPtr, objPtr, origPtr) } return indexPtr; } - + CONST TkTextIndex* TkTextGetIndexFromObj(interp, textPtr, objPtr) Tcl_Interp *interp; /* Use this for error reporting. */ @@ -274,6 +282,62 @@ TkTextNewIndexObj(textPtr, indexPtr) /* *--------------------------------------------------------------------------- * + * TkTextMakePixelIndex -- + * + * Given a pixel index and a byte index, look things up in the B-tree + * and fill in a TkTextIndex structure. + * + * Results: + * + * The structure at *indexPtr is filled in with information about + * the character at pixelIndex (or the closest existing character, + * if the specified one doesn't exist), and the number of excess + * pixels is returned as a result. This means if the given pixel + * index is exactly correct for the top-edge of the indexPtr, then + * zero will be returned, and otherwise we will return the + * calculation 'desired pixelIndex' - 'actual pixel index of + * indexPtr'. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextMakePixelIndex(textPtr, pixelIndex, indexPtr) + TkText* textPtr; /* The Text Widget */ + int pixelIndex; /* pixel-index of desired line (0 means first + * pixel of first line of text). */ + TkTextIndex *indexPtr; /* Structure to fill in. */ +{ + int pixelOffset = 0; + + indexPtr->tree = textPtr->tree; + indexPtr->textPtr = textPtr; + + if (pixelIndex < 0) { + pixelIndex = 0; + } + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, pixelIndex, + &pixelOffset); + if (indexPtr->linePtr == NULL) { + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, + TkBTreeNumPixels(textPtr->tree)-1, &pixelOffset); + indexPtr->byteIndex = 0; + return pixelOffset; + } + indexPtr->byteIndex = 0; + + if (pixelOffset <= 0) { + return 0; + } + return TkTextMeasureDown(textPtr, indexPtr, pixelOffset); +} + +/* + *--------------------------------------------------------------------------- + * * TkTextMakeByteIndex -- * * Given a line index and a byte index, look things up in the B-tree @@ -293,7 +357,7 @@ TkTextNewIndexObj(textPtr, indexPtr) TkTextIndex * TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) - TkTextBTree tree; /* Tree that lineIndex and charIndex refer + TkTextBTree tree; /* Tree that lineIndex and byteIndex refer * to. */ int lineIndex; /* Index of desired line (0 means first * line of text). */ @@ -827,9 +891,9 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) } if ((*cp == '+') || (*cp == '-')) { - cp = ForwBack(cp, indexPtr); + cp = ForwBack(textPtr, cp, indexPtr); } else { - cp = StartEnd(cp, indexPtr); + cp = StartEnd(textPtr, cp, indexPtr); } if (cp == NULL) { goto error; @@ -965,7 +1029,8 @@ TkTextIndexCmp(index1Ptr, index2Ptr) */ static CONST char * -ForwBack(string, indexPtr) +ForwBack(textPtr, string, indexPtr) + TkText *textPtr; /* Information about text widget. */ CONST char *string; /* String to parse for additional info * about modifier (count and units). * Points to "+" or "-" that starts @@ -974,7 +1039,7 @@ ForwBack(string, indexPtr) { register CONST char *p, *units; char *end; - int count, lineIndex; + int count, lineIndex, modifier; size_t length; /* @@ -996,8 +1061,12 @@ ForwBack(string, indexPtr) /* * Find the end of this modifier (next space or + or - character), - * then parse the unit specifier and update the position - * accordingly. + * then check if there is a textual 'display' or 'any' modifier. + * These modifiers can be their own word (in which case they can + * be abbreviated) or they can follow on to the actual unit in + * a single word (in which case no abbreviation is allowed). So, + * 'display lines', 'd lines', 'displaylin' are all ok, but 'dline' + * is not. */ units = p; @@ -1005,43 +1074,166 @@ ForwBack(string, indexPtr) p++; } length = p - units; + if ((*units == 'd') && (strncmp(units, "display", + (length > 7 ? 7 : length)) == 0)) { + modifier = TKINDEX_DISPLAY; + if (length > 7) { + p -= (length - 7); + } + } else if ((*units == 'a') && (strncmp(units, "any", + (length > 3 ? 3 : length)) == 0)) { + modifier = TKINDEX_ANY; + if (length > 3) { + p -= (length - 3); + } + } else { + modifier = TKINDEX_NONE; + } + + /* + * If we had a modifier, which we interpreted ok, so now forward + * to the actual units. + */ + if (modifier != TKINDEX_NONE) { + while (isspace(UCHAR(*p))) { + p++; + } + units = p; + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { + p++; + } + length = p - units; + } + + /* + * Finally parse the units. + */ if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) { + TkTextCountType type; + if (modifier == TKINDEX_NONE) { + type = COUNT_INDICES; + } else if (modifier == TKINDEX_ANY) { + type = COUNT_CHARS; + } else { + type = COUNT_DISPLAY_CHARS; + } if (*string == '+') { - TkTextIndexForwChars(indexPtr, count, indexPtr); + TkTextIndexForwChars(indexPtr, count, indexPtr, type); } else { - TkTextIndexBackChars(indexPtr, count, indexPtr); + TkTextIndexBackChars(indexPtr, count, indexPtr, type); + } + } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) { + TkTextCountType type; + if (modifier == TKINDEX_DISPLAY) { + type = COUNT_DISPLAY_INDICES; + } else { + type = COUNT_INDICES; } - } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); if (*string == '+') { - lineIndex += count; + TkTextIndexForwChars(indexPtr, count, indexPtr, type); + } else { + TkTextIndexBackChars(indexPtr, count, indexPtr, type); + } + } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { + if (modifier == TKINDEX_DISPLAY) { + /* + * Find the appropriate pixel offset of the current position + * within its display line. This also has the side-effect of + * moving indexPtr, but that doesn't matter since we will do + * it again below. + * + * Then find the right display line, and finally calculated + * the index we want in that display line, based on the + * original pixel offset. + */ + int xOffset, forward; + if (TkTextIsElided(textPtr, indexPtr)) { + /* Go forward to the first non-elided index */ + TkTextIndexForwChars(indexPtr, 0, indexPtr, + COUNT_DISPLAY_INDICES | COUNT_IS_ELIDED); + } + /* + * Unlike the Forw/BackChars code, the display line code + * is sensitive to whether we are genuinely going + * forwards or backwards. So, we need to determine that. + * This is important in the case where we have "+ -3 + * displaylines", for example. + */ + if ((count < 0) ^ (*string == '-')) { + forward = 0; + } else { + forward = 1; + } + count = abs(count); + if (count == 0) { + return p; + } + if (forward) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, &xOffset); + while (count-- > 0) { + /* + * Go to the end of the line, then forward one + * char/byte to get to the beginning of the next + * line. + */ + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); + TkTextIndexForwChars(indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } + } else { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, &xOffset); + while (count-- > 0) { + /* + * Go to the beginning of the line, then backward one + * char/byte to get to the end of the previous line + */ + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + TkTextIndexBackChars(indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + } + /* + * This call assumes indexPtr is the beginning of a display line + * and moves it to the 'xOffset' position of that line, which is + * just what we want. + */ + TkTextIndexOfX(textPtr, xOffset, indexPtr); } else { - lineIndex -= count; + lineIndex = TkBTreeLineIndex(indexPtr->linePtr); + if (*string == '+') { + lineIndex += count; + } else { + lineIndex -= count; + + /* + * The check below retains the character position, even + * if the line runs off the start of the file. Without + * it, the character position will get reset to 0 by + * TkTextMakeIndex. + */ + if (lineIndex < 0) { + lineIndex = 0; + } + } /* - * The check below retains the character position, even - * if the line runs off the start of the file. Without - * it, the character position will get reset to 0 by - * TkTextMakeIndex. + * This doesn't work quite right if using a proportional font or + * UTF-8 characters with varying numbers of bytes, or if there + * are embedded windows, images, etc. The cursor will bop + * around, keeping a constant number of bytes (not characters) + * from the left edge (but making sure not to split any UTF-8 + * characters), regardless of the x-position the index + * corresponds to. The proper way to do this is to get the + * x-position of the index and then pick the character at the + * same x-position in the new line. */ - if (lineIndex < 0) { - lineIndex = 0; - } + TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex, + indexPtr); } - /* - * This doesn't work quite right if using a proportional font or - * UTF-8 characters with varying numbers of bytes. The cursor will - * bop around, keeping a constant number of bytes (not characters) - * from the left edge (but making sure not to split any UTF-8 - * characters), regardless of the x-position the index corresponds - * to. The proper way to do this is to get the x-position of the - * index and then pick the character at the same x-position in the - * new line. - */ - - TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex, - indexPtr); } else { return NULL; } @@ -1060,6 +1252,9 @@ ForwBack(string, indexPtr) * *dstPtr is modified to refer to the character "count" bytes after * srcPtr, or to the last character in the TkText if there aren't * "count" bytes left. + * + * In this latter case, the function returns '1' to indicate + * that not all of 'byteCount' could be used. * * Side effects: * None. @@ -1067,7 +1262,7 @@ ForwBack(string, indexPtr) *--------------------------------------------------------------------------- */ -void +int TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) CONST TkTextIndex *srcPtr; /* Source index. */ int byteCount; /* How many bytes forward to move. May be @@ -1080,7 +1275,7 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) if (byteCount < 0) { TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr); - return; + return 0; } *dstPtr = *srcPtr; @@ -1102,13 +1297,13 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) */ if (dstPtr->byteIndex < lineLength) { - return; + return 0; } dstPtr->byteIndex -= lineLength; linePtr = TkBTreeNextLine(dstPtr->linePtr); if (linePtr == NULL) { dstPtr->byteIndex = lineLength - 1; - return; + return 1; } dstPtr->linePtr = linePtr; } @@ -1120,12 +1315,18 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) * TkTextIndexForwChars -- * * Given an index for a text widget, this procedure creates a new - * index that points "count" characters ahead of the source index. + * index that points "count" items of type given by "type" ahead of + * the source index. "count" can be zero, which is useful in + * the case where one wishes to move forward by display + * (non-elided) chars or indices or one wishes to move forward + * by chars, skipping any intervening indices. In this case + * dstPtr will point to the first acceptable index which is + * encountered. * * Results: - * *dstPtr is modified to refer to the character "count" characters + * *dstPtr is modified to refer to the character "count" items * after srcPtr, or to the last character in the TkText if there - * aren't "count" characters left in the file. + * aren't sufficient items left in the widget. * * Side effects: * None. @@ -1134,22 +1335,28 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) */ void -TkTextIndexForwChars(srcPtr, charCount, dstPtr) +TkTextIndexForwChars(srcPtr, charCount, dstPtr, type) CONST TkTextIndex *srcPtr; /* Source index. */ int charCount; /* How many characters forward to move. * May be negative. */ TkTextIndex *dstPtr; /* Destination index: gets modified. */ + TkTextCountType type; /* The type of item to count */ { TkTextLine *linePtr; TkTextSegment *segPtr; int byteOffset; char *start, *end, *p; Tcl_UniChar ch; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); if (charCount < 0) { - TkTextIndexBackChars(srcPtr, -charCount, dstPtr); + TkTextIndexBackChars(srcPtr, -charCount, dstPtr, type); return; } + if (checkElided) { + elide = ((type & COUNT_IS_ELIDED) ? 1 : 0); + } *dstPtr = *srcPtr; @@ -1166,23 +1373,49 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr) */ for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr == &tkTextCharType) { - start = segPtr->body.chars + byteOffset; - end = segPtr->body.chars + segPtr->size; - for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { - if (charCount == 0) { - dstPtr->byteIndex += (p - start); - return; + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current + * segment isn't visible, then we simply continue the + * loop + */ + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + if (elide) { + elide = (segPtr->typePtr == &tkTextToggleOffType) + & !segPtr->body.toggle.tagPtr->elide; + } else { + elide = (segPtr->typePtr == &tkTextToggleOnType) + & segPtr->body.toggle.tagPtr->elide; + } } - charCount--; } - } else { - if (charCount < segPtr->size - byteOffset) { - dstPtr->byteIndex += charCount; - return; + } + + if (!elide) { + if (segPtr->typePtr == &tkTextCharType) { + start = segPtr->body.chars + byteOffset; + end = segPtr->body.chars + segPtr->size; + for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { + if (charCount == 0) { + dstPtr->byteIndex += (p - start); + return; + } + charCount--; + } + } else { + if (type & COUNT_INDICES) { + if (charCount < segPtr->size - byteOffset) { + dstPtr->byteIndex += charCount; + return; + } + charCount -= segPtr->size - byteOffset; + } } - charCount -= segPtr->size - byteOffset; } + dstPtr->byteIndex += segPtr->size - byteOffset; byteOffset = 0; } @@ -1207,6 +1440,155 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr) /* *--------------------------------------------------------------------------- * + * TkTextIndexCount -- + * + * Given an ordered pair of indices in a text widget, this + * procedure counts how many characters (not bytes) are between + * the two indices. + * + * It is illegal to call this procedure with unordered indices. + * + * Note that 'textPtr' is only used if we need to check for + * elided attributes, i.e. if type is COUNT_DISPLAY_INDICES or + * COUNT_DISPLAY_CHARS. + * + * Results: + * The number of characters in the given range, which meet + * the appropriate 'type' attributes. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type) + CONST TkText *textPtr; /* Overall information about text widget. */ + CONST TkTextIndex *indexPtr1;/* Index describing location of + * character from which to count. */ + CONST TkTextIndex *indexPtr2;/* Index describing location of last + * character at which to stop the + * count. */ + TkTextCountType type; /* The kind of indices to count */ +{ + TkTextLine *linePtr1; + TkTextSegment *segPtr, *seg2Ptr = NULL; + int byteOffset, maxBytes; + int count = 0; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); + + /* + * Find seg that contains src index, and remember + * how many bytes not to count in the given segment. + */ + + segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset); + linePtr1 = indexPtr1->linePtr; + + seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes); + + if (checkElided) { + elide = TkTextIsElided(textPtr, indexPtr1); + } + + while (1) { + /* + * Go through each segment in line adding up the number + * of characters. + */ + + for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current + * segment isn't visible, then we simply continue the + * loop + */ + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + if (elide) { + elide = (segPtr->typePtr == &tkTextToggleOffType) + & !segPtr->body.toggle.tagPtr->elide; + } else { + elide = (segPtr->typePtr == &tkTextToggleOnType) + & segPtr->body.toggle.tagPtr->elide; + } + } + } + if (elide) { + if (segPtr == seg2Ptr) { + return count; + } + byteOffset = 0; + continue; + } + } + + if (segPtr->typePtr == &tkTextCharType) { + int byteLen = segPtr->size - byteOffset; + register unsigned char *str = + (unsigned char *) segPtr->body.chars + byteOffset; + register int i; + + if (segPtr == seg2Ptr) { + if (byteLen > (maxBytes - byteOffset)) { + byteLen = maxBytes - byteOffset; + } + } + i = byteLen; + + /* + * This is a speed sensitive function, so run specially over the + * string to count continuous ascii characters before resorting + * to the Tcl_NumUtfChars call. This is a long form of: + * stringPtr->numChars = + * Tcl_NumUtfChars(objPtr->bytes, objPtr->length); + */ + + while (i && (*str < 0xC0)) { i--; str++; } + count += byteLen - i; + if (i) { + count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset + + (byteLen - i), i); + } + } else { + if (type & COUNT_INDICES) { + int byteLen = segPtr->size - byteOffset; + if (segPtr == seg2Ptr) { + if (byteLen > (maxBytes - byteOffset)) { + byteLen = maxBytes - byteOffset; + } + } + count += byteLen; + } + } + if (segPtr == seg2Ptr) { + return count; + } + byteOffset = 0; + } + + /* + * Go to the next line. If we are at the end of the text item, + * back up one byte (for the terminal '\n' character) and return + * that index. + */ + + linePtr1 = TkBTreeNextLine(linePtr1); + if (linePtr1 == NULL) { + panic("Reached end of text widget when counting characters"); + } + segPtr = linePtr1->segPtr; + } +} + +/* + *--------------------------------------------------------------------------- + * * TkTextIndexBackBytes -- * * Given an index for a text widget, this procedure creates a new @@ -1274,12 +1656,18 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) * TkTextIndexBackChars -- * * Given an index for a text widget, this procedure creates a new - * index that points "count" characters earlier than the source index. + * index that points "count" items of type given by "type" earlier + * than the source index. "count" can be zero, which is useful in + * the case where one wishes to move backward by display + * (non-elided) chars or indices or one wishes to move backward by + * chars, skipping any intervening indices. In this case the + * returned index *dstPtr will point just _after_ the first + * acceptable index which is encountered. * * Results: - * *dstPtr is modified to refer to the character "count" characters - * before srcPtr, or to the first character in the file if there - * aren't "count" characters earlier than srcPtr. + * *dstPtr is modified to refer to the character "count" items + * before srcPtr, or to the first index in the window if there + * aren't sufficient items earlier than srcPtr. * * Side effects: * None. @@ -1288,21 +1676,27 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) */ void -TkTextIndexBackChars(srcPtr, charCount, dstPtr) +TkTextIndexBackChars(srcPtr, charCount, dstPtr, type) CONST TkTextIndex *srcPtr; /* Source index. */ int charCount; /* How many characters backward to move. * May be negative. */ TkTextIndex *dstPtr; /* Destination index: gets modified. */ + TkTextCountType type; /* The type of item to count */ { TkTextSegment *segPtr, *oldPtr; int lineIndex, segSize; CONST char *p, *start, *end; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); - if (charCount <= 0) { - TkTextIndexForwChars(srcPtr, -charCount, dstPtr); + if (charCount < 0) { + TkTextIndexForwChars(srcPtr, -charCount, dstPtr, type); return; } - + if (checkElided) { + elide = ((type & COUNT_IS_ELIDED) ? 1 : 0); + } + *dstPtr = *srcPtr; /* @@ -1320,25 +1714,50 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) segSize -= segPtr->size; } while (1) { - if (segPtr->typePtr == &tkTextCharType) { - start = segPtr->body.chars; - end = segPtr->body.chars + segSize; - for (p = end; ; p = Tcl_UtfPrev(p, start)) { - if (charCount == 0) { - dstPtr->byteIndex -= (end - p); - return; - } - if (p == start) { - break; + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current + * segment isn't visible, then we simply continue the + * loop + */ + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + if (segPtr->body.toggle.tagPtr->elideString != NULL) { + if (elide) { + elide = (segPtr->typePtr == &tkTextToggleOnType) + & !segPtr->body.toggle.tagPtr->elide; + } else { + elide = (segPtr->typePtr == &tkTextToggleOffType) + & segPtr->body.toggle.tagPtr->elide; + } } - charCount--; } - } else { - if (charCount <= segSize) { - dstPtr->byteIndex -= charCount; - return; + } + + if (!elide) { + if (segPtr->typePtr == &tkTextCharType) { + start = segPtr->body.chars; + end = segPtr->body.chars + segSize; + for (p = end; ; p = Tcl_UtfPrev(p, start)) { + if (charCount == 0) { + dstPtr->byteIndex -= (end - p); + return; + } + if (p == start) { + break; + } + charCount--; + } + } else { + if (type & COUNT_INDICES) { + if (charCount <= segSize) { + dstPtr->byteIndex -= charCount; + return; + } + charCount -= segSize; + } } - charCount -= segSize; } dstPtr->byteIndex -= segSize; @@ -1405,18 +1824,20 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) */ static CONST char * -StartEnd(string, indexPtr) +StartEnd(textPtr, string, indexPtr) + TkText *textPtr; /* Information about text widget. */ CONST char *string; /* String to parse for additional info * about modifier (count and units). * Points to first character of modifer * word. */ - TkTextIndex *indexPtr; /* Index to mdoify based on string. */ + TkTextIndex *indexPtr; /* Index to modify based on string. */ { CONST char *p; int c, offset; size_t length; register TkTextSegment *segPtr; - + int modifier; + /* * Find the end of the modifier word. */ @@ -1425,17 +1846,57 @@ StartEnd(string, indexPtr) /* Empty loop body. */ } length = p-string; + if ((*string == 'd') && (strncmp(string, "display", + (length > 7 ? 7 : length)) == 0)) { + modifier = TKINDEX_DISPLAY; + if (length > 7) { + p -= (length - 7); + } + } else if ((*string == 'a') && (strncmp(string, "any", + (length > 3 ? 3 : length)) == 0)) { + modifier = TKINDEX_ANY; + if (length > 3) { + p -= (length - 3); + } + } else { + modifier = TKINDEX_NONE; + } + + /* + * If we had a modifier, which we interpreted ok, so now forward + * to the actual units. + */ + if (modifier != TKINDEX_NONE) { + while (isspace(UCHAR(*p))) { + p++; + } + string = p; + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { + p++; + } + length = p - string; + } + if ((*string == 'l') && (strncmp(string, "lineend", length) == 0) && (length >= 5)) { - indexPtr->byteIndex = 0; - for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; - segPtr = segPtr->nextPtr) { - indexPtr->byteIndex += segPtr->size; + if (modifier == TKINDEX_DISPLAY) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); + } else { + indexPtr->byteIndex = 0; + for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; + segPtr = segPtr->nextPtr) { + indexPtr->byteIndex += segPtr->size; + } + /* We know '\n' is encoded with a single byte index */ + indexPtr->byteIndex -= sizeof(char); } - indexPtr->byteIndex -= sizeof(char); } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) && (length >= 5)) { - indexPtr->byteIndex = 0; + if (modifier == TKINDEX_DISPLAY) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + } else { + indexPtr->byteIndex = 0; + } } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0) && (length >= 5)) { int firstChar = 1; @@ -1446,6 +1907,10 @@ StartEnd(string, indexPtr) * a character that isn't part of a word and stop there. */ + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } segPtr = TkTextIndexToSeg(indexPtr, &offset); while (1) { if (segPtr->typePtr == &tkTextCharType) { @@ -1462,12 +1927,21 @@ StartEnd(string, indexPtr) } } if (firstChar) { - TkTextIndexForwChars(indexPtr, 1, indexPtr); + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } else { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_INDICES); + } } } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0) && (length >= 5)) { int firstChar = 1; + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } /* * Starting with the current character, look for one that's not * part of a word and keep moving backward until you find one. @@ -1495,7 +1969,12 @@ StartEnd(string, indexPtr) } } if (!firstChar) { - TkTextIndexForwChars(indexPtr, 1, indexPtr); + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_DISPLAY_INDICES + | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr))); + } else { + TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_INDICES); + } } } else { return NULL; diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c index d48e10a..7fcb275 100644 --- a/generic/tkTextMark.c +++ b/generic/tkTextMark.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextMark.c,v 1.8 2003/05/19 14:37:20 dkf Exp $ + * RCS: @(#) $Id: tkTextMark.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $ */ #include "tkInt.h" @@ -253,7 +253,7 @@ TkTextMarkCmd(textPtr, interp, objc, objv) TkTextSegment * TkTextSetMark(textPtr, name, indexPtr) TkText *textPtr; /* Text widget in which to create mark. */ - CONST char *name; /* Name of mark to set. */ + CONST char *name; /* Name of mark to set. */ TkTextIndex *indexPtr; /* Where to set mark. */ { Tcl_HashEntry *hPtr; @@ -274,11 +274,15 @@ TkTextSetMark(textPtr, name, indexPtr) if (markPtr == textPtr->insertMarkPtr) { TkTextIndex index, index2; TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); + /* + * While we wish to redisplay, no heights have changed, so + * no need to call TkTextInvalidateLineMetrics. + */ TkTextChanged(textPtr, &index, &index2); if (TkBTreeLineIndex(indexPtr->linePtr) == TkBTreeNumLines(textPtr->tree)) { - TkTextIndexBackChars(indexPtr, 1, &insertIndex); + TkTextIndexBackChars(indexPtr, 1, &insertIndex, COUNT_INDICES); indexPtr = &insertIndex; } } @@ -303,7 +307,11 @@ TkTextSetMark(textPtr, name, indexPtr) if (markPtr == textPtr->insertMarkPtr) { TkTextIndex index2; - TkTextIndexForwChars(indexPtr, 1, &index2); + TkTextIndexForwChars(indexPtr, 1, &index2, COUNT_INDICES); + /* + * While we wish to redisplay, no heights have changed, so + * no need to call TkTextInvalidateLineMetrics + */ TkTextChanged(textPtr, indexPtr, &index2); } return markPtr; @@ -540,9 +548,20 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) * corresponds to y. */ { TkText *textPtr = (TkText *) chunkPtr->clientData; + TkTextIndex index; int halfWidth = textPtr->insertWidth/2; - - if ((x + halfWidth) < 0) { + int rightSideWidth; + int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0; + + if(textPtr->insertCursorType) { + TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); + TkTextCharBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth); + rightSideWidth = charWidth + halfWidth; + } else { + rightSideWidth = halfWidth; + } + + if ((x + rightSideWidth) < 0) { /* * The insertion cursor is off-screen. * Indicate caret at 0,0 and return. @@ -564,11 +583,11 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) if (textPtr->flags & INSERT_ON) { Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, - x - halfWidth, y, textPtr->insertWidth, height, + x - halfWidth, y, charWidth + textPtr->insertWidth, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED); } else if (textPtr->selBorder == textPtr->insertBorder) { Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border, - x - halfWidth, y, textPtr->insertWidth, height, + x - halfWidth, y, charWidth + textPtr->insertWidth, height, 0, TK_RELIEF_FLAT); } } diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c index e3bf289..00a9193 100644 --- a/generic/tkTextTag.c +++ b/generic/tkTextTag.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextTag.c,v 1.10 2003/05/27 15:35:53 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextTag.c,v 1.11 2003/10/31 09:02:12 vincentdarley Exp $ */ #include "default.h" @@ -162,7 +162,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) } tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3])); for (i = 4; i < objc; i += 2) { - if (TkTextGetObjIndex(interp, textPtr, objv[i], &index1) != TCL_OK) { + if (TkTextGetObjIndex(interp, textPtr, objv[i], + &index1) != TCL_OK) { return TCL_ERROR; } if (objc > (i+1)) { @@ -175,7 +176,7 @@ TkTextTagCmd(textPtr, interp, objc, objv) } } else { index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); + TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES); } if (tagPtr->affectsDisplay) { @@ -188,38 +189,40 @@ TkTextTagCmd(textPtr, interp, objc, objv) TkTextEventuallyRepick(textPtr); } - TkBTreeTag(&index1, &index2, tagPtr, addTag); - - /* - * If the tag is "sel" then grab the selection if we're supposed - * to export it and don't already have it. Also, invalidate - * partially-completed selection retrievals. - */ - - if (tagPtr == textPtr->selTagPtr) { - XEvent event; + if (TkBTreeTag(&index1, &index2, tagPtr, addTag)) { /* - * Send an event that the selection changed. - * This is equivalent to - * "event generate $textWidget <>" + * If the tag is "sel", and we actually adjusted + * something then grab the selection if we're + * supposed to export it and don't already have it. + * Also, invalidate partially-completed selection + * retrievals. */ - memset((VOID *) &event, 0, sizeof(event)); - event.xany.type = VirtualEvent; - event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); - event.xany.send_event = False; - event.xany.window = Tk_WindowId(textPtr->tkwin); - event.xany.display = Tk_Display(textPtr->tkwin); - ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); - Tk_HandleEvent(&event); - - if (addTag && textPtr->exportSelection - && !(textPtr->flags & GOT_SELECTION)) { - Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, - TkTextLostSelection, (ClientData) textPtr); - textPtr->flags |= GOT_SELECTION; + if (tagPtr == textPtr->selTagPtr) { + XEvent event; + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <>" + */ + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = VirtualEvent; + event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.xany.send_event = False; + event.xany.window = Tk_WindowId(textPtr->tkwin); + event.xany.display = Tk_Display(textPtr->tkwin); + ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event); + + if (addTag && textPtr->exportSelection + && !(textPtr->flags & GOT_SELECTION)) { + Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, + TkTextLostSelection, (ClientData) textPtr); + textPtr->flags |= GOT_SELECTION; + } + textPtr->abortSelections = 1; } - textPtr->abortSelections = 1; } } break; @@ -464,26 +467,37 @@ TkTextTagCmd(textPtr, interp, objc, objv) textPtr->selFgColorPtr = tagPtr->fgColor; } tagPtr->affectsDisplay = 0; - if ((tagPtr->border != NULL) - || (tagPtr->reliefString != NULL) - || (tagPtr->bgStipple != None) - || (tagPtr->fgColor != NULL) || (tagPtr->tkfont != None) - || (tagPtr->fgStipple != None) + tagPtr->affectsDisplayGeometry = 0; + if ((tagPtr->elideString != NULL) + || (tagPtr->tkfont != None) || (tagPtr->justifyString != NULL) || (tagPtr->lMargin1String != NULL) || (tagPtr->lMargin2String != NULL) || (tagPtr->offsetString != NULL) - || (tagPtr->overstrikeString != NULL) || (tagPtr->rMarginString != NULL) || (tagPtr->spacing1String != NULL) || (tagPtr->spacing2String != NULL) || (tagPtr->spacing3String != NULL) || (tagPtr->tabStringPtr != NULL) - || (tagPtr->underlineString != NULL) - || (tagPtr->elideString != NULL) || (tagPtr->wrapMode != TEXT_WRAPMODE_NULL)) { tagPtr->affectsDisplay = 1; + tagPtr->affectsDisplayGeometry = 1; + } + if ((tagPtr->border != NULL) + || (tagPtr->reliefString != NULL) + || (tagPtr->bgStipple != None) + || (tagPtr->fgColor != NULL) + || (tagPtr->fgStipple != None) + || (tagPtr->overstrikeString != NULL) + || (tagPtr->underlineString != NULL)) { + tagPtr->affectsDisplay = 1; } + /* + * This line is totally unnecessary if this is a new + * tag, since it can't possibly have been applied to + * anything yet. We might wish to test for that + * case specially + */ TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, tagPtr, 1); return result; @@ -582,7 +596,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) case TAG_NAMES: { TkTextTag **arrayPtr; int arraySize; - + Tcl_Obj *listObj; + if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 3, objv, "?index?"); return TCL_ERROR; @@ -609,10 +624,13 @@ TkTextTagCmd(textPtr, interp, objc, objv) } } SortTags(arraySize, arrayPtr); + listObj = Tcl_NewListObj(0, NULL); for (i = 0; i < arraySize; i++) { tagPtr = arrayPtr[i]; - Tcl_AppendElement(interp, tagPtr->name); + Tcl_ListObjAppendElement(interp, listObj, + Tcl_NewStringObj(tagPtr->name,-1)); } + Tcl_SetObjResult(interp, listObj); ckfree((char *) arrayPtr); break; } @@ -778,7 +796,7 @@ TkTextTagCmd(textPtr, interp, objc, objv) } case TAG_RANGES: { TkTextSearch tSearch; - char position[TK_POS_CHARS]; + Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "tagName"); @@ -793,13 +811,14 @@ TkTextTagCmd(textPtr, interp, objc, objv) 0, &last); TkBTreeStartSearch(&first, &last, tagPtr, &tSearch); if (TkBTreeCharTagged(&first, tagPtr)) { - TkTextPrintIndex(&first, position); - Tcl_AppendElement(interp, position); + Tcl_ListObjAppendElement(interp, listObj, + TkTextNewIndexObj(textPtr, &first)); } while (TkBTreeNextTag(&tSearch)) { - TkTextPrintIndex(&tSearch.curIndex, position); - Tcl_AppendElement(interp, position); + Tcl_ListObjAppendElement(interp, listObj, + TkTextNewIndexObj(textPtr, &tSearch.curIndex)); } + Tcl_SetObjResult(interp, listObj); break; } } @@ -883,6 +902,7 @@ TkTextCreateTag(textPtr, tagName) tagPtr->elide = 0; tagPtr->wrapMode = TEXT_WRAPMODE_NULL; tagPtr->affectsDisplay = 0; + tagPtr->affectsDisplayGeometry = 0; textPtr->numTags++; Tcl_SetHashValue(hPtr, tagPtr); tagPtr->optionTable = Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs); @@ -1132,7 +1152,7 @@ TkTextBindProc(clientData, eventPtr) # define AnyButtonMask (Button1Mask|Button2Mask|Button3Mask\ |Button4Mask|Button5Mask) - Tcl_Preserve((ClientData) textPtr); + textPtr->refCount++; /* * This code simulates grabs for mouse buttons by keeping track @@ -1197,12 +1217,16 @@ TkTextBindProc(clientData, eventPtr) oldState = eventPtr->xbutton.state; eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask |Button3Mask|Button4Mask|Button5Mask); - TkTextPickCurrent(textPtr, eventPtr); + if (!(textPtr->flags & DESTROYED)) { + TkTextPickCurrent(textPtr, eventPtr); + } eventPtr->xbutton.state = oldState; } done: - Tcl_Release((ClientData) textPtr); + if (--textPtr->refCount == 0) { + ckfree((char *) textPtr); + } } /* @@ -1224,7 +1248,7 @@ TkTextBindProc(clientData, eventPtr) * then the commands associated with character entry and leave * could do just about anything. For example, the text widget * might be deleted. It is up to the caller to protect itself - * with calls to Tcl_Preserve and Tcl_Release. + * by incrementing the refCount of the text widget. * *-------------------------------------------------------------- */ diff --git a/generic/tkTextWind.c b/generic/tkTextWind.c index cc0449e..25991ac 100644 --- a/generic/tkTextWind.c +++ b/generic/tkTextWind.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextWind.c,v 1.7 2003/05/19 13:04:24 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextWind.c,v 1.8 2003/10/31 09:02:12 vincentdarley Exp $ */ #include "tk.h" @@ -59,10 +59,6 @@ static void EmbWinDelayedUnmap _ANSI_ARGS_(( ClientData clientData)); static int EmbWinDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr, int treeGone)); -static void EmbWinDisplayProc _ANSI_ARGS_(( - TkTextDispChunk *chunkPtr, int x, int y, - int lineHeight, int baseline, Display *display, - Drawable dst, int screenY)); static int EmbWinLayoutProc _ANSI_ARGS_((TkText *textPtr, TkTextIndex *indexPtr, TkTextSegment *segPtr, int offset, int maxX, int maxChars, @@ -231,6 +227,13 @@ TkTextWindowCmd(textPtr, interp, objc, objv) } } else { TkTextChanged(textPtr, &index, &index); + /* + * It's probably not true that all window configuration + * can change the line height, so we could be more + * efficient here and only call this when necessary. + */ + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); return EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4); } break; @@ -289,10 +292,12 @@ TkTextWindowCmd(textPtr, interp, objc, objv) if (EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4) != TCL_OK) { TkTextIndex index2; - TkTextIndexForwChars(&index, 1, &index2); + TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES); TkBTreeDeleteChars(&index, &index2); return TCL_ERROR; } + TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TK_TEXT_INVALIDATE_ONLY); break; } case WIND_NAMES: { @@ -463,6 +468,8 @@ EmbWinStructureProc(clientData, eventPtr) index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* @@ -497,6 +504,8 @@ EmbWinRequestProc(clientData, tkwin) index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* @@ -542,6 +551,8 @@ EmbWinLostSlaveProc(clientData, tkwin) index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* @@ -763,7 +774,7 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, * Fill in the chunk structure. */ - chunkPtr->displayProc = EmbWinDisplayProc; + chunkPtr->displayProc = TkTextEmbWinDisplayProc; chunkPtr->undisplayProc = EmbWinUndisplayProc; chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL; chunkPtr->bboxProc = EmbWinBboxProc; @@ -819,7 +830,7 @@ EmbWinCheckProc(ewPtr, linePtr) /* *-------------------------------------------------------------- * - * EmbWinDisplayProc -- + * TkTextEmbWinDisplayProc -- * * This procedure is invoked by the text displaying code * when it is time to actually draw an embedded window @@ -835,8 +846,9 @@ EmbWinCheckProc(ewPtr, linePtr) *-------------------------------------------------------------- */ -static void -EmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) +void +TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, + display, dst, screenY) TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in dst at which to * draw this chunk (differs from @@ -848,8 +860,10 @@ EmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) * the chunk itself). */ int lineHeight; /* Total height of line. */ int baseline; /* Offset of baseline from y. */ - Display *display; /* Display to use for drawing. */ - Drawable dst; /* Pixmap or window in which to draw */ + Display *display; /* Display to use for drawing + * (unused). */ + Drawable dst; /* Pixmap or window in which to draw + * (unused). */ int screenY; /* Y-coordinate in text window that * corresponds to y. */ { diff --git a/library/text.tcl b/library/text.tcl index 7a3a699..dffd473 100644 --- a/library/text.tcl +++ b/library/text.tcl @@ -3,7 +3,7 @@ # This file defines the default bindings for Tk text widgets and provides # procedures that help in implementing the bindings. # -# RCS: @(#) $Id: text.tcl,v 1.26 2003/10/06 22:14:26 jenglish Exp $ +# RCS: @(#) $Id: text.tcl,v 1.27 2003/10/31 09:02:13 vincentdarley Exp $ # # Copyright (c) 1992-1994 The Regents of the University of California. # Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -88,10 +88,10 @@ bind Text { %W mark set insert @%x,%y } bind Text { - tk::TextSetCursor %W insert-1c + tk::TextSetCursor %W insert-1displayindices } bind Text { - tk::TextSetCursor %W insert+1c + tk::TextSetCursor %W insert+1displayindices } bind Text { tk::TextSetCursor %W [tk::TextUpDownLine %W -1] @@ -100,10 +100,10 @@ bind Text { tk::TextSetCursor %W [tk::TextUpDownLine %W 1] } bind Text { - tk::TextKeySelect %W [%W index {insert - 1c}] + tk::TextKeySelect %W [%W index {insert - 1displayindices}] } bind Text { - tk::TextKeySelect %W [%W index {insert + 1c}] + tk::TextKeySelect %W [%W index {insert + 1displayindices}] } bind Text { tk::TextKeySelect %W [tk::TextUpDownLine %W -1] @@ -205,7 +205,7 @@ bind Text { if {[%W cget -autoseparators]} {%W edit separator} } bind Text { - if {[string compare [%W tag nextrange sel 1.0 end] ""]} { + if {[%W tag nextrange sel 1.0 end] ne ""} { %W delete sel.first sel.last } else { %W delete insert @@ -213,7 +213,7 @@ bind Text { } } bind Text { - if {[string compare [%W tag nextrange sel 1.0 end] ""]} { + if {[%W tag nextrange sel 1.0 end] ne ""} { %W delete sel.first sel.last } elseif {[%W compare insert != 1.0]} { %W delete insert-1c @@ -290,7 +290,7 @@ bind Text { } bind Text { if {!$tk_strictMotif} { - tk::TextSetCursor %W insert-1c + tk::TextSetCursor %W insert-1displayindices } } bind Text { @@ -305,7 +305,7 @@ bind Text { } bind Text { if {!$tk_strictMotif} { - tk::TextSetCursor %W insert+1c + tk::TextSetCursor %W insert+1displayindices } } bind Text { @@ -449,12 +449,19 @@ bind Text { } set ::tk::Priv(prevPos) {} -# The MouseWheel will typically only fire on Windows. However, -# someone could use the "event generate" command to produce one -# on other platforms. +# The MouseWheel will typically only fire on Windows and MacOS X. +# However, someone could use the "event generate" command to produce one +# on other platforms. We must be careful not to round -ve values of %D +# down to zero. bind Text { - %W yview scroll [expr {- (%D / 120) * 4}] units + %W yview scroll [expr {-%D}] pixels +} +if {[string equal [tk windowingsystem] "classic"] + || [string equal [tk windowingsystem] "aqua"]} { +bind Text { + %W yview scroll [expr {- 10*%D}] pixels +} } if {[string equal "x11" [tk windowingsystem]]} { @@ -464,12 +471,12 @@ if {[string equal "x11" [tk windowingsystem]]} { # http://www.inria.fr/koala/colas/mouse-wheel-scroll/ bind Text <4> { if {!$tk_strictMotif} { - %W yview scroll -5 units + %W yview scroll -50 pixels } } bind Text <5> { if {!$tk_strictMotif} { - %W yview scroll 5 units + %W yview scroll 50 pixels } } } @@ -582,7 +589,7 @@ proc ::tk::TextSelectTo {w x y {extend 0}} { } } } - if {$Priv(mouseMoved) || [string compare $Priv(selectMode) "char"]} { + if {$Priv(mouseMoved) || ($Priv(selectMode) ne "char")} { $w tag remove sel 0.0 end $w mark set insert $cur $w tag add sel $first $last @@ -659,9 +666,9 @@ proc ::tk::TextAutoScan {w} { variable ::tk::Priv if {![winfo exists $w]} return if {$Priv(y) >= [winfo height $w]} { - $w yview scroll 2 units + $w yview scroll [expr {1 + $Priv(y) - [winfo height $w]}] pixels } elseif {$Priv(y) < 0} { - $w yview scroll -2 units + $w yview scroll [expr {-1 + $Priv(y)}] pixels } elseif {$Priv(x) >= [winfo width $w]} { $w xview scroll 2 units } elseif {$Priv(x) < 0} { @@ -821,28 +828,30 @@ proc ::tk::TextInsert {w s} { } # ::tk::TextUpDownLine -- -# Returns the index of the character one line above or below the -# insertion cursor. There are two tricky things here. First, -# we want to maintain the original column across repeated operations, -# even though some lines that will get passed through don't have -# enough characters to cover the original column. Second, don't -# try to scroll past the beginning or end of the text. +# Returns the index of the character one display line above or below the +# insertion cursor. There are two tricky things here. First, we want to +# maintain the original x position across repeated operations, even though +# some lines that will get passed through don't have enough characters to +# cover the original column. Second, don't try to scroll past the +# beginning or end of the text. # # Arguments: # w - The text window in which the cursor is to move. -# n - The number of lines to move: -1 for up one line, +# n - The number of display lines to move: -1 for up one line, # +1 for down one line. proc ::tk::TextUpDownLine {w n} { variable ::tk::Priv set i [$w index insert] - scan $i "%d.%d" line char - if {[string compare $Priv(prevPos) $i]} { - set Priv(char) $char - } - set new [$w index [expr {$line + $n}].$Priv(char)] - if {[$w compare $new == end] || [$w compare $new == "insert linestart"]} { + if {$Priv(prevPos) ne $i} { + set Priv(textPosOrig) $i + } + set lines [$w count -displaylines $Priv(textPosOrig) $i] + set new [$w index \ + "$Priv(textPosOrig) + [expr {$lines + $n}] displaylines"] + if {[$w compare $new == end] \ + || [$w compare $new == "insert display linestart"]} { set new $i } set Priv(prevPos) $new @@ -862,10 +871,11 @@ proc ::tk::TextPrevPara {w pos} { set pos [$w index "$pos linestart"] while {1} { if {([string equal [$w get "$pos - 1 line"] "\n"] \ - && [string compare [$w get $pos] "\n"]) \ + && ([$w get $pos] ne "\n")) \ || [string equal $pos "1.0"]} { - if {[regexp -indices {^[ ]+(.)} [$w get $pos "$pos lineend"] \ - dummy index]} { + if {[regexp -indices -- \ + {^[ \t]+(.)} [$w get $pos "$pos lineend"] \ + dummy index]} { set pos [$w index "$pos + [lindex $index 0] chars"] } if {[$w compare $pos != insert] || [string equal $pos 1.0]} { @@ -887,7 +897,7 @@ proc ::tk::TextPrevPara {w pos} { proc ::tk::TextNextPara {w start} { set pos [$w index "$start linestart + 1 line"] - while {[string compare [$w get $pos] "\n"]} { + while {[$w get $pos] ne "\n"} { if {[$w compare $pos == end]} { return [$w index "end - 1c"] } @@ -899,7 +909,7 @@ proc ::tk::TextNextPara {w start} { return [$w index "end - 1c"] } } - if {[regexp -indices {^[ ]+(.)} [$w get $pos "$pos lineend"] \ + if {[regexp -indices -- {^[ \t]+(.)} [$w get $pos "$pos lineend"] \ dummy index]} { return [$w index "$pos + [lindex $index 0] chars"] } @@ -996,7 +1006,7 @@ proc ::tk_textPaste w { $w configure -autoseparators 0 $w edit separator } - if {[string compare [tk windowingsystem] "x11"]} { + if {[tk windowingsystem] ne "x11"} { catch { $w delete sel.first sel.last } } $w insert insert $sel @@ -1041,16 +1051,10 @@ proc ::tk::TextNextPos {w start op} { set text "" set cur $start while {[$w compare $cur < end]} { - set text $text[$w get $cur "$cur lineend + 1c"] + set text $text[$w get -displaychars $cur "$cur lineend + 1c"] set pos [$op $text 0] if {$pos >= 0} { - ## Adjust for embedded windows and images - ## dump gives us 3 items per window/image - set dump [$w dump -image -window $start "$start + $pos c"] - if {[llength $dump]} { - set pos [expr {$pos + ([llength $dump]/3)}] - } - return [$w index "$start + $pos c"] + return [$w index "$start + $pos display chars"] } set cur [$w index "$cur lineend +1c"] } @@ -1070,22 +1074,10 @@ proc ::tk::TextPrevPos {w start op} { set text "" set cur $start while {[$w compare $cur > 0.0]} { - set text [$w get "$cur linestart - 1c" $cur]$text + set text [$w get -displaychars "$cur linestart - 1c" $cur]$text set pos [$op $text end] if {$pos >= 0} { - ## Adjust for embedded windows and images - ## dump gives us 3 items per window/image - set dump [$w dump -image -window "$cur linestart" "$start - 1c"] - if {[llength $dump]} { - ## This is a hokey extra hack for control-arrow movement - ## that should be in a while loop to be correct (hobbs) - if {[$w compare [lindex $dump 2] > \ - "$cur linestart - 1c + $pos c"]} { - incr pos -1 - } - set pos [expr {$pos + ([llength $dump]/3)}] - } - return [$w index "$cur linestart - 1c + $pos c"] + return [$w index "$cur linestart - 1c + $pos display chars"] } set cur [$w index "$cur linestart - 1c"] } diff --git a/mac/tkMacDefault.h b/mac/tkMacDefault.h index 64901d4..a708ee3 100644 --- a/mac/tkMacDefault.h +++ b/mac/tkMacDefault.h @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkMacDefault.h,v 1.16 2002/06/21 23:09:55 hobbs Exp $ + * RCS: @(#) $Id: tkMacDefault.h,v 1.17 2003/10/31 09:02:14 vincentdarley Exp $ */ #ifndef _TKMACDEFAULT @@ -471,6 +471,7 @@ #define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR NORMAL_BG #define DEF_TEXT_BG_MONO WHITE +#define DEF_TEXT_BLOCK_CURSOR "0" #define DEF_TEXT_BORDER_WIDTH "0" #define DEF_TEXT_CURSOR "xterm" #define DEF_TEXT_FG BLACK diff --git a/macosx/tkMacOSXDefault.h b/macosx/tkMacOSXDefault.h index 13a05ce..17d7d24 100644 --- a/macosx/tkMacOSXDefault.h +++ b/macosx/tkMacOSXDefault.h @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkMacOSXDefault.h,v 1.2 2002/08/31 06:12:29 das Exp $ + * RCS: @(#) $Id: tkMacOSXDefault.h,v 1.3 2003/10/31 09:02:15 vincentdarley Exp $ */ #ifndef _TKMACDEFAULT @@ -473,6 +473,7 @@ #define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR NORMAL_BG #define DEF_TEXT_BG_MONO WHITE +#define DEF_TEXT_BLOCK_CURSOR "0" #define DEF_TEXT_BORDER_WIDTH "0" #define DEF_TEXT_CURSOR "xterm" #define DEF_TEXT_FG BLACK diff --git a/tests/text.test b/tests/text.test index 6f8a72e..0622a03 100644 --- a/tests/text.test +++ b/tests/text.test @@ -6,11 +6,12 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: text.test,v 1.22 2003/05/19 21:19:52 dkf Exp $ +# RCS: @(#) $Id: text.test,v 1.23 2003/10/31 09:02:15 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv tcltest::loadTestedCommands +namespace import -force tcltest::test # Create entries in the option database to be sure that geometry options # like border width have predictable values. @@ -53,6 +54,7 @@ foreach test { {-background #ff00ff #ff00ff } {-bd 4 4 foo} {-bg blue blue #xx} + {-blockcursor 0 0 xx} {-borderwidth 7 7 ++} {-cursor watch watch lousy} {-exportselection no 0 maybe} @@ -112,7 +114,7 @@ test text-1.[incr i] {text options} { lappend result [lindex $i 4] } set result -} {1 blue {} {} 7 watch 0 {} fixed #012 5 #123 #234 0 green 45 100 47 2 5 3 82 raised #ffff01234567 21 yellow 0 0 0 0 disabled {1i 2i 3i 4i} {any old thing} 1 73 word {x scroll command} {test command}} +} {1 blue {} {} 0 7 watch 0 {} fixed #012 5 #123 #234 0 green 45 100 47 2 5 3 82 raised #ffff01234567 21 yellow 0 0 0 0 disabled {1i 2i 3i 4i} {any old thing} 1 73 word {x scroll command} {test command}} test text-2.1 {Tk_TextCmd procedure} { list [catch {text} msg] $msg @@ -151,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, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, 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, replace, scan, search, see, tag, window, xview, or yview}} test text-4.1 {TextWidgetCmd procedure, "bbox" option} { list [catch {.t bbox} msg] $msg @@ -219,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, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, 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, replace, scan, search, see, tag, window, xview, or yview}} # "configure" option is already covered above @@ -228,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, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, 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, replace, scan, search, see, tag, window, xview, or yview}} test text-7.3 {TextWidgetCmd procedure, "debug" option} { .t debug true .t deb @@ -324,9 +326,30 @@ test text-8.16 {TextWidgetCmd procedure, "delete" option} { .t delete 1.0 end; .t insert 1.0 $prevtext +test text-8.17 {TextWidgetCmd procedure, "replace" option} { + list [catch {.t replace 1.3 2.3} err] $err +} {1 {wrong # args: should be ".t replace index1 index2 chars ?tagList chars tagList ...?"}} +test text-8.18 {TextWidgetCmd procedure, "replace" option} { + list [catch {.t replace 3.1 2.3 foo} err] $err +} {1 {Index "2.3" before "3.1" in the text.}} +test text-8.19 {TextWidgetCmd procedure, "replace" option} { + list [catch {.t replace 2.1 2.3 foo} err] $err +} {0 {}} +.t delete 1.0 end; .t insert 1.0 $prevtext +test text-8.20 {TextWidgetCmd procedure, "replace" option} { + .t configure -undo 1 + # Ensure it is treated as a single undo action + .t replace 2.1 2.3 foo + .t edit undo + .t configure -undo 0 + string equal [.t get 1.0 end-1c] $prevtext +} {1} + +.t delete 1.0 end; .t insert 1.0 $prevtext + test text-9.1 {TextWidgetCmd procedure, "get" option} { list [catch {.t get} msg] $msg -} {1 {wrong # args: should be ".t get index1 ?index2 ...?"}} +} {1 {wrong # args: should be ".t get ?-displaychars? ?--? index1 ?index2 ...?"}} test text-9.2 {TextWidgetCmd procedure, "get" option} { list [catch {.t get a b c} msg] $msg } {1 {bad text index "a"}} @@ -376,9 +399,208 @@ test text-9.15 {TextWidgetCmd procedure, "get" option} { .t get 5.2 5.4 5.4 5.5 end-3c end } {{y } G { 7 }} +test text-9.16 {TextWidgetCmd procedure, "get" option} { + .t get 5.2 5.3 5.4 5.3 +} {y} +test text-9.17 {TextWidgetCmd procedure, "get" option} { + .t index "5.2 +3 indices" +} {5.5} test text-9.17 {TextWidgetCmd procedure, "get" option} { + .t index "5.2 +3chars" +} {5.5} +test text-9.17 {TextWidgetCmd procedure, "get" option} { + .t index "5.2 +3displayindices" +} {5.5} +.t tag configure elide -elide 1 +.t tag add elide 5.2 5.4 +test text-9.18 {TextWidgetCmd procedure, "get" option} { list [catch {.t get 5.2 5.4 5.5 foo} msg] $msg } {1 {bad text index "foo"}} +test text-9.19 {TextWidgetCmd procedure, "get" option} { + .t get 5.2 5.4 5.4 5.5 end-3c end +} {{y } G { 7 +}} +test text-9.20 {TextWidgetCmd procedure, "get" option} { + .t get -displaychars 5.2 5.4 5.4 5.5 end-3c end +} {{} G { 7 +}} +test text-9.21 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.1 +4indices"] [.t index "5.1+4d indices"] +} {5.5 5.7} +test text-9.22 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.1 +4a chars"] [.t index "5.1+4d chars"] +} {5.5 5.7} +test text-9.23 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.5 -4indices"] [.t index "5.7-4d indices"] +} {5.1 5.1} +test text-9.24 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.5 -4a chars"] [.t index "5.7-4d chars"] +} {5.1 5.1} +.t window create 5.4 +test text-9.25 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.1 +4indices"] [.t index "5.1+4d indices"] +} {5.5 5.7} +test text-9.26 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.1 +4a chars"] [.t index "5.1+4d chars"] +} {5.6 5.8} +test text-9.25 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.5 -4indices"] [.t index "5.7-4d indices"] +} {5.1 5.1} +test text-9.26 {TextWidgetCmd procedure, "get" option} { + list [.t index "5.6 -4a chars"] [.t index "5.8-4d chars"] +} {5.1 5.1} +.t delete 5.4 +.t tag add elide 5.5 5.6 +test text-9.23 {TextWidgetCmd procedure, "get" option} { + .t get -displaychars 5.2 5.8 +} {Grl} +.t tag delete elide +.t mark unset a +.t mark unset b +.t mark unset c +test text-9.2.1 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count} msg] $msg +} {1 {wrong # args: should be ".t count ?options? index1 index2"}} +test text-9.2.2.1 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count blah 1.0 2.0} msg] $msg +} {1 {bad option "blah" must be -chars, -displaychars, -displayindices, -displaylines, -indices, -lines, -update, -xpixels, or -ypixels}} +test text-9.2.2 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count a b} msg] $msg +} {1 {bad text index "a"}} +test text-9.2.3 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count @q 3.1} msg] $msg +} {1 {bad text index "@q"}} +test text-9.2.4 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count 3.1 @r} msg] $msg +} {1 {bad text index "@r"}} +test text-9.2.5 {TextWidgetCmd procedure, "count" option} { + .t count 5.7 5.3 +} {-4} +test text-9.2.6 {TextWidgetCmd procedure, "count" option} { + .t count 5.3 5.5 +} {2} +test text-9.2.7 {TextWidgetCmd procedure, "count" option} { + .t count 5.3 end +} {29} +.t mark set a 5.3 +.t mark set b 5.3 +.t mark set c 5.5 +test text-9.2.8 {TextWidgetCmd procedure, "count" option} { + .t count 5.2 5.7 +} {5} +test text-9.2.9 {TextWidgetCmd procedure, "count" option} { + .t count 5.2 5.3 +} {1} +test text-9.2.10 {TextWidgetCmd procedure, "count" option} { + .t count 5.2 5.4 +} {2} +test text-9.2.17 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count 5.2 foo} msg] $msg +} {1 {bad text index "foo"}} +.t tag configure elide -elide 1 +.t tag add elide 2.2 3.4 +.t tag add elide 4.0 4.1 +test text-9.2.18 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices 2.0 3.0 +} {2} +test text-9.2.19 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices 2.2 3.0 +} {0} +test text-9.2.20 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices 2.0 4.2 +} {5} +# Create one visible and one invisible window +frame .t.w1 +frame .t.w2 +.t mark set a 2.2 +# Creating this window here means that the elidden text +# now starts at 2.3, but 'a' is automatically moved to 2.3 +.t window create 2.1 -window .t.w1 +.t window create 3.1 -window .t.w2 +test text-9.2.21 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices 2.0 3.0 +} {3} +test text-9.2.22 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices 2.2 3.0 +} {1} +test text-9.2.23 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices a 3.0 +} {0} +test text-9.2.24 {TextWidgetCmd procedure, "count" option} { + .t count -displayindices 2.0 4.2 +} {6} +test text-9.2.25 {TextWidgetCmd procedure, "count" option} { + .t count -displaychars 2.0 3.0 +} {2} +test text-9.2.26 {TextWidgetCmd procedure, "count" option} { + .t count -displaychars 2.2 3.0 +} {1} +test text-9.2.27 {TextWidgetCmd procedure, "count" option} { + .t count -displaychars a 3.0 +} {0} +test text-9.2.28 {TextWidgetCmd procedure, "count" option} { + .t count -displaychars 2.0 4.2 +} {5} +test text-9.2.29 {TextWidgetCmd procedure, "count" option} { + list [.t count -indices 2.2 3.0] [.t count 2.2 3.0] +} {10 10} +test text-9.2.30 {TextWidgetCmd procedure, "count" option} { + list [.t count -indices a 3.0] [.t count a 3.0] +} {9 9} +test text-9.2.31 {TextWidgetCmd procedure, "count" option} { + .t count -indices 2.0 4.2 +} {21} +test text-9.2.32 {TextWidgetCmd procedure, "count" option} { + .t count -chars 2.2 3.0 +} {10} +test text-9.2.33 {TextWidgetCmd procedure, "count" option} { + .t count -chars a 3.0 +} {9} +test text-9.2.34 {TextWidgetCmd procedure, "count" option} { + .t count -chars 2.0 4.2 +} {19} +destroy .t.w1 +destroy .t.w2 +set current [.t get 1.0 end-1c] +.t delete 1.0 end +.t insert end [string repeat "abcde " 50]\n +.t insert end [string repeat "fghij " 50]\n +.t insert end [string repeat "klmno " 50] +test text-9.2.35 {TextWidgetCmd procedure, "count" option} { + .t count -lines 1.0 end +} {3} +test text-9.2.36 {TextWidgetCmd procedure, "count" option} { + .t count -lines end 1.0 +} {-3} +test text-9.2.37 {TextWidgetCmd procedure, "count" option} { + list [catch {.t count -lines 1.0 2.0 3.0} res] $res +} {1 {bad option "1.0" must be -chars, -displaychars, -displayindices, -displaylines, -indices, -lines, -update, -xpixels, or -ypixels}} +test text-9.2.38 {TextWidgetCmd procedure, "count" option} { + .t count -lines end end +} {0} +test text-9.2.39 {TextWidgetCmd procedure, "count" option} { + .t count -lines 1.5 2.5 +} {1} +test text-9.2.40 {TextWidgetCmd procedure, "count" option} { + .t count -lines 2.5 "2.5 lineend" +} {0} +test text-9.2.41 {TextWidgetCmd procedure, "count" option} { + .t count -lines 2.7 "1.0 lineend" +} {-1} +test text-9.2.42 {TextWidgetCmd procedure, "count" option} { + set old_wrap [.t cget -wrap] + .t configure -wrap none + set res [.t count -displaylines 1.0 end] + .t configure -wrap $old_wrap + set res +} {3} +test text-9.2.43 {TextWidgetCmd procedure, "count" option} { + .t count -lines -chars -indices -displaylines 1.0 end +} {3 903 903 45} +.t configure -wrap none +.t delete 1.0 end +.t insert end $current +unset current test text-10.1 {TextWidgetCmd procedure, "index" option} { list [catch {.t index} msg] $msg @@ -388,7 +610,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, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, 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, replace, scan, search, see, 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"}} @@ -932,7 +1154,7 @@ test text-19.3 {TkTextLostSelection procedure} { .t insert end "xxyz xyz x. the\nfoo -forward bar xxxxx BaR foo\nxyz xxyzx" test text-20.1 {TextSearchCmd procedure, argument parsing} { list [catch {.t search -} msg] $msg -} {1 {bad switch "-": must be --, -all, -backward, -count, -elide, -exact, -forward, -nocase, -nolinestop, or -regexp}} +} {1 {bad switch "-": must be --, -all, -backward, -count, -elide, -exact, -forward, -nocase, -nolinestop, -overlap, -regexp, or -strictlimits}} test text-20.2 {TextSearchCmd procedure, -backwards option} { .t search -backwards xyz 1.4 } {1.1} @@ -960,7 +1182,7 @@ test text-20.8 {TextSearchCmd procedure, -nocase option} { } {2.13 2.23} test text-20.9 {TextSearchCmd procedure, -n ambiguous option} { list [catch {.t search -n BaR 1.1} msg] $msg -} {1 {bad switch "-n": must be --, -all, -backward, -count, -elide, -exact, -forward, -nocase, -nolinestop, or -regexp}} +} {1 {bad switch "-n": must be --, -all, -backward, -count, -elide, -exact, -forward, -nocase, -nolinestop, -overlap, -regexp, or -strictlimits}} test text-20.9.1 {TextSearchCmd procedure, -nocase option} { .t search -noc BaR 1.1 } {2.13} @@ -1112,10 +1334,16 @@ test text-20.47 {TextSearchCmd procedure, checking stopIndex} { } {{} 2.13 2.13 {}} test text-20.48 {TextSearchCmd procedure, checking stopIndex} { list [.t search -backwards bar 2.20 2.13] \ - [.t search -backwards bar 2.20 2.14] \ - [.t search -backwards bar 2.14 2.13] \ - [.t search -backwards bar 2.13 2.13] + [.t search -backwards bar 2.20 2.14] \ + [.t search -backwards bar 2.14 2.13] \ + [.t search -backwards bar 2.13 2.13] } {2.13 {} 2.13 {}} +test text-20.48.1 {TextSearchCmd procedure, checking stopIndex} { + list [.t search -backwards -strict bar 2.20 2.13] \ + [.t search -backwards -strict bar 2.20 2.14] \ + [.t search -backwards -strict bar 2.14 2.13] \ + [.t search -backwards -strict bar 2.13 2.13] +} {2.13 {} {} {}} test text-20.49 {TextSearchCmd procedure, embedded windows and index/count} { frame .t.f1 -width 20 -height 20 -relief raised -bd 2 frame .t.f2 -width 20 -height 20 -relief raised -bd 2 @@ -1174,6 +1402,9 @@ test text-20.61 {TextSearchCmd procedure, special cases} { test text-20.62 {TextSearchCmd, freeing copy of pattern} { # This test doesn't return a result, but it will generate # a core leak if the pattern copy isn't properly freed. + # (actually in Tk 8.5 objectification means there is no + # longer a copy of the pattern, but we leave this test in + # anyway). set p abcdefg1234567890 set p $p$p$p$p$p$p$p$p @@ -1276,6 +1507,16 @@ test text-20.75 {TextSearchCmd, hidden text inside match must count in length} { .t2 tag add hidden 1.2 1.4 list [.t2 search -count foo foar 1.3] $foo } {1.0 6} +test text-20.75.1 {TextSearchCmd, hidden text inside match must count in length} { + deleteWindows + pack [text .t2] + .t2 insert end "foobar\nfoobar\nfoobar" + .t2 tag configure hidden -elide true + .t2 tag add hidden 1.2 1.4 + list \ + [.t2 search -strict -count foo foar 1.3] \ + [.t2 search -strict -count foo foar 2.3] $foo +} {{} 1.0 6} test text-20.76 {TextSearchCmd, hidden text and start index} { deleteWindows pack [text .t2] @@ -1311,6 +1552,15 @@ test text-20.78.1 {TextSearchCmd, hidden text inside match must count in length} test text-20.78.2 {TextSearchCmd, hidden text inside match must count in length} { deleteWindows pack [text .t2] + .t2 insert end "foobar\nfoobar\nfoobar" + .t2 tag configure hidden -elide true + .t2 tag add hidden 1.2 1.4 + .t2 search -strict -count foo foar 1.3 +} {} + +test text-20.78.3 {TextSearchCmd, hidden text inside match must count in length} { + deleteWindows + pack [text .t2] .t2 insert end "foobar\nfoobar\nfoar" .t2 tag configure hidden -elide true .t2 tag add hidden 1.2 1.4 @@ -1318,7 +1568,7 @@ test text-20.78.2 {TextSearchCmd, hidden text inside match must count in length} list [.t2 search -regexp -all -count foo foar 1.3] $foo } {{2.0 3.0 1.0} {6 4 6}} -test text-20.78.3 {TextSearchCmd, hidden text inside match must count in length} { +test text-20.78.4 {TextSearchCmd, hidden text inside match must count in length} { deleteWindows pack [text .t2] .t2 insert end "foobar\nfoobar\nfoar" @@ -1328,6 +1578,16 @@ test text-20.78.3 {TextSearchCmd, hidden text inside match must count in length} list [.t2 search -all -count foo foar 1.3] $foo } {{2.0 3.0 1.0} {6 4 6}} +test text-20.78.5 {TextSearchCmd, hidden text inside match must count in length} { + deleteWindows + pack [text .t2] + .t2 insert end "foobar\nfoobar\nfoar" + .t2 tag configure hidden -elide true + .t2 tag add hidden 1.2 1.4 + .t2 tag add hidden 2.2 2.4 + list [.t2 search -strict -all -count foo foar 1.3] $foo +} {{2.0 3.0} {6 4}} + test text-20.79 {TextSearchCmd, multiline matching} { deleteWindows pack [text .t2] @@ -1457,9 +1717,16 @@ test text-20.97 {TextSearchCmd, multiline matching} { deleteWindows pack [text .t2] .t2 insert end "foobar\nfoobar\nfoobar" - list [.t2 search -backwards -regexp -count foo foobar\nfoo 1.0] $foo + list [.t2 search -backwards -regexp -count foo foobar\nfoo end] $foo } {2.0 10} +test text-20.97.1 {TextSearchCmd, multiline matching} { + deleteWindows + pack [text .t2] + .t2 insert end "foobar\nfoobar\nfoobar" + list [.t2 search -backwards -regexp -count foo foobar\nfo end] $foo +} {2.0 9} + test text-20.98 {TextSearchCmd, multiline matching} { deleteWindows pack [text .t2] @@ -1521,7 +1788,7 @@ static Tcl_Obj* FSNormalizeAbsolutePath } {} test text-20.106 {TextSearchCmd, multiline regexp matching} { - # Practical example which crashes Tk, but only after the + # Practical example which used to crash Tk, but only after the # search is complete. This is memory corruption caused by # a bug in Tcl's handling of string objects. # (Tcl bug 635200) @@ -1598,7 +1865,7 @@ test text-20.114 {TextSearchCmd, wrapping and limits} { pack [text .t2] .t2 insert end "if (stringPtr->uallocated > 0) \{x" .t2 search -backwards -all -regexp -- "\[\]\")\}\[(\{\]" "1.32" 1.0 -} {1.3 1.29 1.31} +} {1.31 1.29 1.3} test text-20.115 {TextSearchCmd, wrapping and limits} { deleteWindows @@ -1687,6 +1954,13 @@ test text-20.124 {TextSearchCmd, regexp linestop} { deleteWindows pack [text .t2] .t2 insert 1.0 "first line\nlast line of text" + .t2 search -regexp -all -overlap -- {i.*x} 1.0 +} {2.6} + +test text-20.124.1 {TextSearchCmd, regexp linestop} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "first line\nlast line of text" .t2 search -regexp -all -- {i.*x} 1.0 } {2.6} @@ -1694,8 +1968,15 @@ test text-20.125 {TextSearchCmd, multiline regexp nolinestop matching} { deleteWindows pack [text .t2] .t2 insert 1.0 "first line\nlast line of text" - .t2 search -regexp -all -nolinestop -- {i.*x} 1.0 -} {1.1 1.7 2.6} + list [.t2 search -regexp -all -overlap -count c -nolinestop -- {i.*x} 1.0] $c +} {{1.1 2.6} {26 10}} + +test text-20.125.1 {TextSearchCmd, multiline regexp nolinestop matching} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "first line\nlast line of text" + list [.t2 search -regexp -all -count c -nolinestop -- {i.*x} 1.0] $c +} {1.1 26} test text-20.126 {TextSearchCmd, stop at end of line} { deleteWindows @@ -1703,13 +1984,453 @@ test text-20.126 {TextSearchCmd, stop at end of line} { .t2 insert 1.0 " \t\n last line of text" .t2 search -regexp -nolinestop -- {[^ \t]} 1.0 } {1.3} +test text-20.127 {TextSearchCmd, overlapping all matches} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde abcde" + list [.t2 search -regexp -all -overlap -count c -- {\w+} 1.0] $c +} {{1.0 1.6} {5 5}} +test text-20.127.1 {TextSearchCmd, non-overlapping all matches} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde abcde" + list [.t2 search -regexp -all -count c -- {\w+} 1.0] $c +} {{1.0 1.6} {5 5}} +test text-20.128 {TextSearchCmd, stop at end of line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde abcde" + list [.t2 search -backwards -regexp -all -count c -- {\w+} 1.0] $c +} {{1.6 1.0} {5 5}} +test text-20.129 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + list [.t2 search -backwards -regexp -count c -- {Z\w+} 1.21 1.5] $c +} {1.8 8} +test text-20.130 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + list [.t2 search -backwards -all -overlap -regexp -count c -- {Z\w+} 1.21 1.5] $c +} {1.8 8} +test text-20.130.1 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + list [.t2 search -backwards -all -regexp -count c -- {Z\w+} 1.21 1.5] $c +} {1.8 8} +test text-20.131 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + list [.t2 search -backwards -overlap -all -regexp -count c -- {Z\w+} 1.21 1.1] $c +} {1.4 12} +test text-20.131.1 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + list [.t2 search -backwards -overlap -all -regexp -count c -- {Z[^Z]+Z} 1.21 1.1] $c +} {{1.8 1.4} {5 5}} +test text-20.131.2 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + list [.t2 search -backwards -all -regexp -count c -- {Z\w+} 1.21 1.1] $c +} {1.4 12} +test text-20.132 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + .t2 insert 1.0 "bla ZabcZdefZghi and some text again\n" + list [.t2 search -backwards -all -overlap -regexp -count c -- {Z\w+} 2.21 1.5] $c +} {{2.4 1.8} {12 8}} +test text-20.132.1 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + .t2 insert 1.0 "bla ZabcZdefZghi and some text again\n" + list [.t2 search -backwards -all -regexp -count c -- {Z\w+} 2.21 1.5] $c +} {{2.4 1.8} {12 8}} +test text-20.133 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + .t2 insert 1.0 "bla ZabcZdefZghi and some text again\n" + list [.t2 search -backwards -overlap -all -regexp -count c -- {Z\w+} 2.21 1.1] $c +} {{2.4 1.4} {12 12}} +test text-20.133.1 {TextSearchCmd, backwards search stop index } { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "bla ZabcZdefZghi and some text again" + .t2 insert 1.0 "bla ZabcZdefZghi and some text again\n" + list [.t2 search -backwards -all -regexp -count c -- {Z\w+} 2.21 1.1] $c +} {{2.4 1.4} {12 12}} +test text-20.134 {TextSearchCmd, search -all example} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 { -test text-20.126 {TextSearchCmd, stop at end of line} { +See the package: supersearch for more information. + + +See the package: incrementalSearch for more information. + +package: Brws . + + +See the package: marks for more information. + +} + set pat {package: ([a-zA-Z0-9][-a-zA-Z0-9._+#/]*)} + list [.t2 search -nolinestop -regexp -nocase -all -forwards \ + -count c -- $pat 1.0 end] $c +} {{3.8 6.8 8.0 11.8} {20 26 13 14}} + +test text-20.135 {TextSearchCmd, backwards search overlaps} { deleteWindows pack [text .t2] - .t2 insert 1.0 " \t\n last line of text" - .t2 search -regexp -- {[^ \t]} 1.0 -} {2.3} + .t2 insert 1.0 "foobarfoobaaaaaaaaaaarfoo" + .t2 search -backwards -regexp {fooba+rfoo} end +} {1.6} +test text-20.135.1 {TextSearchCmd, backwards search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foobarfoobaaaaaaaaaaarfoo" + .t2 search -backwards -overlap -all -regexp {fooba+rfoo} end +} {1.6 1.0} + +test text-20.135.2 {TextSearchCmd, backwards search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foobarfoobaaaaaaaaaaarfoo" + .t2 search -backwards -all -regexp {fooba+rfoo} end +} {1.6} + +test text-20.135.3 {TextSearchCmd, forwards search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foobarfoobaaaaaaaaaaarfoo" + .t2 search -all -overlap -regexp {fooba+rfoo} end +} {1.0 1.6} + +test text-20.135.4 {TextSearchCmd, forwards search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foobarfoobaaaaaaaaaaarfoo" + .t2 search -all -regexp {fooba+rfoo} end +} {1.0} + +test text-20.136 {TextSearchCmd, forward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abababab" + .t2 search -exact -overlap -all {abab} 1.0 +} {1.0 1.2 1.4} + +test text-20.136.1 {TextSearchCmd, forward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abababab" + .t2 search -exact -all {abab} 1.0 +} {1.0 1.4} + +test text-20.137 {TextSearchCmd, backward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "ababababab" + .t2 search -exact -overlap -backwards -all {abab} end +} {1.6 1.4 1.2 1.0} + +test text-20.137.1 {TextSearchCmd, backward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "ababababab" + .t2 search -exact -backwards -all {abab} end +} {1.6 1.2} + +test text-20.137.2 {TextSearchCmd, backward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abababababab" + .t2 search -exact -backwards -all {abab} end +} {1.8 1.4 1.0} + +test text-20.138 {TextSearchCmd, forward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foo\nbar\nfoo\nbar\nfoo\nbar\nfoo\n" + .t2 search -exact -overlap -all "foo\nbar\nfoo" 1.0 +} {1.0 3.0 5.0} + +test text-20.138.1 {TextSearchCmd, forward exact search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foo\nbar\nfoo\nbar\nfoo\nbar\nfoo\n" + .t2 search -exact -all "foo\nbar\nfoo" 1.0 +} {1.0 5.0} + +test text-20.139 {TextSearchCmd, backward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foo\nbar\nfoo\nbar\nfoo\nbar\nfoo\n" + .t2 search -exact -overlap -backward -all "foo\nbar\nfoo" end +} {5.0 3.0 1.0} + +test text-20.140 {TextSearchCmd, backward exact search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foo\nbar\nfoo\nbar\nfoo\nbar\nfoo\n" + .t2 search -exact -backward -all "foo\nbar\nfoo" end +} {5.0 1.0} + +test text-20.141 {TextSearchCmd, backward exact search overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foo\nbar\nfoo\nbar\nfoo\nbar\nfoo\n" + .t2 search -regexp -backward -overlap -all "foo\nbar\nfoo" end +} {5.0 3.0 1.0} + +test text-20.142 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "foo\nbar\nfoo\nbar\nfoo\nbar\nfoo\n" + .t2 search -regexp -backward -all "foo\nbar\nfoo" end +} {5.0 1.0} + +test text-20.142 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -- {(\$)?[\w:_]+} 1.9 +} {1.7} + +test text-20.143 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -- {(\$)?[\w:_]+} 1.9 1.5 +} {1.7} + +test text-20.144 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -- {(\$)?[\w:_]+} 1.9 1.7 +} {1.7} + +test text-20.145 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -- {(\$)?[\w:_]+} 1.9 1.8 +} {1.8} + +test text-20.146 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -all -- {(\$)?[\w:_]+} 1.9 1.3 +} {1.7 1.3} + +test text-20.147 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -all -- {(\$)?[\w:_]+} 1.9 1.13 +} {} + +test text-20.148 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -all -- {(\$)?[\w:_]+} 2.0 1.3 +} {1.12 1.7 1.3} + +test text-20.149 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 " aasda asdj werwer" + .t2 search -regexp -backward -all -- {(\$)?[\w:_]+} 1.3 +} {1.1 1.12 1.7 1.3} + +test text-20.150 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\n" + .t2 search -regexp -backward -all -- {(\w+\n)+} end +} {1.0} + +test text-20.151 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\n" + .t2 search -regexp -backward -all -- {(\w+\n)+} end 1.5 +} {2.0} + +test text-20.152 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + .t2 search -regexp -backward -all -- {(\w+\n\w)+} end 1.5 +} {2.0} + +test text-20.153 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -all -count foo -- {(\w+\n)+} 1.0] $foo +} {1.0 20} + +test text-20.154 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + set res {} + lappend res \ + [list [.t2 search -regexp -all -count foo -- {(\w+\n)+} 1.0] $foo] \ + [list [.t2 search -regexp -all -count foo -- {(\w+)+} 1.0] $foo] +} {{1.0 20} {{1.0 2.0 3.0 4.0} {5 5 5 1}}} + +test text-20.155 {TextSearchCmd, regexp search greedy} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -all -nolinestop -count foo -- {.*} 1.0] $foo +} {1.0 20} + +test text-20.156 {TextSearchCmd, regexp search greedy} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -all -count foo -- {.*} 1.0] $foo +} {{1.0 2.0 3.0 4.0} {5 5 5 1}} + +test text-20.157 {TextSearchCmd, regexp search greedy multi-line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -count foo -- {(\w+\n\w)+} 1.0] $foo +} {1.0 19} + +test text-20.158 {TextSearchCmd, regexp search greedy multi-line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -backwards -count foo -- {(\w+\n\w)+} end] $foo +} {1.0 19} + +test text-20.159 {TextSearchCmd, regexp search greedy multi-line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -all -backwards -count foo -- {(\w+\n\w)+} end] $foo +} {1.0 19} + +test text-20.160 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + .t2 search -regexp -backward -all -- {(\w+\n\w)+} end 1.5 +} {2.0} + +test text-20.161 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + .t2 search -regexp -backward -all -- {(\w+\n\w)+} end 1.3 +} {1.3} + +test text-20.162 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -forward -count foo -- {(\w+\n\w)+} 1.3] $foo +} {1.3 16} + +test text-20.163 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -forward -all -count foo -- {(\w+\n\w)+} 1.3] $foo + # This result is somewhat debatable -- the two results do overlap, + # but only because the search has totally wrapped around back to + # the start. +} {{1.3 1.0} {16 19}} + +test text-20.164 {TextSearchCmd, backward regexp search no-overlaps} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "abcde\nabcde\nabcde\na" + list [.t2 search -regexp -forward -all -count foo -- {(\w+\n\w)+} 1.0 1.3] $foo +} {1.0 19} + +test text-20.165 {TextSearchCmd, regexp search multi-line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\naaaa\nbbbb\n" + list [.t2 search -regexp -forward -all -count foo -- {(a+\n(b+\n))+} 1.0] $foo +} {1.0 20} + +test text-20.166 {TextSearchCmd, regexp search complex cases} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\naaaa\nbbbb\n" + list [.t2 search -regexp -forward -all -count foo \ + -- {(a+\n(b+\n))+} 1.0] $foo +} {1.0 20} + +test text-20.167 {TextSearchCmd, regexp search multi-line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\ncccc\nbbbb\naaaa\n" + set foo {} + list [.t2 search -regexp -forward -all -count foo \ + -- {(b+\nc+\nb+)\na+} 1.0] $foo +} {2.0 19} + +test text-20.168 {TextSearchCmd, regexp search multi-line} {knownBug} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\ncccc\nbbbb\naaaa\n" + set foo {} + list [.t2 search -regexp -forward -all -count foo \ + -- {(a+|b+\nc+\nb+)\na+} 1.0] $foo +} {2.0 19} + +test text-20.169 {TextSearchCmd, regexp search multi-line} {knownBug} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\ncccc\nbbbb\naaaa\n" + set foo {} + list [.t2 search -regexp -forward -all -count foo \ + -- {(a+|b+\nc+\nb+)+\na+} 1.0] $foo +} {2.0 19} + +test text-20.170 {TextSearchCmd, regexp search multi-line} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\ncccc\nbbbb\naaaa\n" + set foo {} + list [.t2 search -regexp -forward -all -count foo \ + -- {((a+|b+\nc+\nb+)+\n)+a+} 1.0] $foo +} {1.0 24} + +test text-20.171 {TextSearchCmd, regexp search multi-line} {knownBug} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\nbbbb\nbbbb\nbbbb\n" + list [.t2 search -regexp -backward -all -count foo \ + -- {b+\n|a+\n(b+\n)+} end] $foo +} {1.0 25} + +test text-20.172 {TextSearchCmd, regexp search multi-line} {knownBug} { + deleteWindows + pack [text .t2] + .t2 insert 1.0 "aaaa\nbbbb\nbbbb\nbbbb\nbbbb\n" + .t2 search -regexp -backward -- {b+\n|a+\n(b+\n)+} end + # Should match at 1.0 for a true greedy match +} {1.0} deleteWindows text .t2 -highlightthickness 0 -bd 0 -relief flat -padx 0 -width 100 diff --git a/tests/textDisp.test b/tests/textDisp.test index 79310b0..da8659b 100644 --- a/tests/textDisp.test +++ b/tests/textDisp.test @@ -6,11 +6,12 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: textDisp.test,v 1.9 2003/04/01 21:06:53 dgp Exp $ +# RCS: @(#) $Id: textDisp.test,v 1.10 2003/10/31 09:02:16 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv tcltest::loadTestedCommands +namespace import -force tcltest::test # The procedure below is used as the scrolling command for the text; # it just saves the scrolling information in a variable "scrollInfo". @@ -37,6 +38,7 @@ option add *Text.highlightThickness 2 # because some window managers don't allow the overall width of a window # to get very narrow. +catch {destroy .f .t} frame .f -width 100 -height 20 pack append . .f left @@ -547,7 +549,7 @@ test textDisp-4.11 {UpdateDisplayInfo, filling in extra vertical space} { } {6.40 {13.0 7.0 6.80 6.60 6.40} {6.40 6.60 6.80 7.0 13.0}} test textDisp-4.12 {UpdateDisplayInfo, filling in extra vertical space} { .t delete 1.0 end - .t insert end "1\n2\n3\n4\n5\n7\n8\n9\n10\n11\n12\n13" + .t insert end "1\n2\n3\n4\n5\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16" button .b -text "Test" -bd 2 -highlightthickness 2 .t window create 3.end -window .b .t yview moveto 1 @@ -793,7 +795,7 @@ test textDisp-6.7 {DisplayText, vertical scrollbar updates} { .t delete 1.0 end update set scrollInfo -} {0 1} +} {0.0 1.0} test textDisp-6.8 {DisplayText, vertical scrollbar updates} { .t configure -wrap char .t delete 1.0 end @@ -805,7 +807,7 @@ test textDisp-6.8 {DisplayText, vertical scrollbar updates} { } update set scrollInfo -} {0 0.769231} +} {0.0 0.769230769231} .t configure -yscrollcommand {} -xscrollcommand scroll test textDisp-6.9 {DisplayText, horizontal scrollbar updates} { .t configure -wrap none @@ -817,7 +819,7 @@ test textDisp-6.9 {DisplayText, horizontal scrollbar updates} { .t insert end xxxxxxxxxxxxxxxxxxxxxxxxxx update set scrollInfo -} {0 0.363636} +} {0.0 0.363636363636} # The following group of tests is marked non-portable because # they result in a lot of extra redisplay under Ultrix. I don't @@ -1020,10 +1022,12 @@ test textDisp-8.11 {TkTextChanged, scrollbar notification when changes are off-s update set scrollInfo "" .t insert end "a\nb\nc\n" - update + # We need to wait for our asychronous callbacks to update the + # scrollbar + update ; after 50; update .t configure -yscrollcommand "" set scrollInfo -} {0 0.625} +} {0.0 0.625} test textDisp-9.1 {TkTextRedrawTag} { .t configure -wrap char @@ -1209,12 +1213,12 @@ test textDisp-11.6 {TkTextSetYView} { } {28.0 {28.0 29.0}} test textDisp-11.7 {TkTextSetYView} { .t yview 30.0 - update + update ; update set tk_textRedraw {} .t yview -pickplace 26.0 update list [.t index @0,0] $tk_textRedraw -} {22.0 {22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0}} +} {21.0 {21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0}} test textDisp-11.8 {TkTextSetYView} { .t yview 30.0 update @@ -1230,7 +1234,7 @@ test textDisp-11.9 {TkTextSetYView} { .t yview -pickplace 43.0 update list [.t index @0,0] $tk_textRedraw -} {39.0 {40.0 41.0 42.0 43.0 44.0 45.0 46.0 47.0 48.0}} +} {38.0 {40.0 41.0 42.0 43.0 44.0 45.0 46.0 47.0 48.0}} test textDisp-11.10 {TkTextSetYView} { .t yview 30.0 update @@ -1257,7 +1261,7 @@ test textDisp-11.12 {TkTextSetYView, wrapped line is off-screen} { list [.t index @0,0] $tk_textRedraw } {2.0 10.20} .t delete 10.0 11.0 -test textDisp-11.13 {TkTestSetYView, partially-visible last line} { +test textDisp-11.13 {TkTestSetYView, partially visible last line} { catch {destroy .top} toplevel .top wm geometry .top +0+0 @@ -1276,8 +1280,10 @@ test textDisp-11.13 {TkTestSetYView, partially-visible last line} { set tk_textRedraw {} .top.t see 5.0 update + # Note, with smooth scrolling, the results of this test + # have changed, and the old '2.0 {5.0 6.0}' is quite wrong. list [.top.t index @0,0] $tk_textRedraw -} {2.0 {5.0 6.0}} +} {1.0 5.0} catch {destroy .top} toplevel .top wm geometry .top +0+0 @@ -1299,7 +1305,8 @@ test textDisp-11.15 {TkTextSetYView, only a few lines visible} { update .top.t see 11.0 .top.t index @0,0 -} {10.0} + # Thie index 9.0 should be just visible by a couple of pixels +} {9.0} test textDisp-11.16 {TkTextSetYView, only a few lines visible} { .top.t yview 8.0 update @@ -1311,7 +1318,8 @@ test textDisp-11.17 {TkTextSetYView, only a few lines visible} { update .top.t see 4.0 .top.t index @0,0 -} {3.0} + # Thie index 2.0 should be just visible by a couple of pixels +} {2.0} destroy .top .t configure -wrap word @@ -1323,21 +1331,21 @@ test textDisp-12.1 {MeasureUp} { .t yview -pickplace 52.0 update .t index @0,0 -} {50.0} +} {49.0} test textDisp-12.2 {MeasureUp} { .t yview 100.0 update .t yview -pickplace 53.0 update .t index @0,0 -} {50.15} +} {50.0} test textDisp-12.3 {MeasureUp} { .t yview 100.0 update .t yview -pickplace 50.10 update .t index @0,0 -} {46.0} +} {45.0} .t configure -wrap none test textDisp-12.4 {MeasureUp} { .t yview 100.0 @@ -1345,14 +1353,14 @@ test textDisp-12.4 {MeasureUp} { .t yview -pickplace 53.0 update .t index @0,0 -} {49.0} +} {48.0} test textDisp-12.5 {MeasureUp} { .t yview 100.0 update .t yview -pickplace 50.10 update .t index @0,0 -} {46.0} +} {45.0} .t configure -wrap none .t delete 1.0 end @@ -1394,7 +1402,7 @@ test textDisp-13.6 {TkTextSeeCmd procedure} { set x [.t index @0,0] .t configure -wrap none set x -} {28.0} +} {27.0} test textDisp-13.7 {TkTextSeeCmd procedure} {fonts} { .t xview moveto 0 .t yview moveto 0 @@ -1463,7 +1471,7 @@ test textDisp-14.1 {TkTextXviewCmd procedure} { .t insert end "xxxx xxxxxxxxx xxxxxxxxxxxxx" .t xview moveto .5 .t xview -} {0.5 0.857143} +} {0.5 0.857142857143} .t configure -wrap char test textDisp-14.2 {TkTextXviewCmd procedure} { .t delete 1.0 end @@ -1472,7 +1480,7 @@ test textDisp-14.2 {TkTextXviewCmd procedure} { .t insert end "xxxxx\n" .t insert end "xxxx" .t xview -} {0 1} +} {0.0 1.0} .t configure -wrap none test textDisp-14.3 {TkTextXviewCmd procedure} { .t delete 1.0 end @@ -1481,7 +1489,7 @@ test textDisp-14.3 {TkTextXviewCmd procedure} { .t insert end "xxxxx\n" .t insert end "xxxx" .t xview -} {0 1} +} {0.0 1.0} test textDisp-14.4 {TkTextXviewCmd procedure} { list [catch {.t xview moveto} msg] $msg } {1 {wrong # args: should be ".t xview moveto fraction"}} @@ -1498,7 +1506,7 @@ test textDisp-14.7 {TkTextXviewCmd procedure} { .t insert end "xxxx xxxxxxxxx xxxxxxxxxxxxx" .t xview moveto .3 .t xview -} {0.303571 0.660714} +} {0.303571428571 0.660714285714} test textDisp-14.8 {TkTextXviewCmd procedure} { .t delete 1.0 end .t insert end xxxxxxxxx\n @@ -1506,7 +1514,7 @@ test textDisp-14.8 {TkTextXviewCmd procedure} { .t insert end "xxxx xxxxxxxxx xxxxxxxxxxxxx" .t xview moveto -.4 .t xview -} {0 0.357143} +} {0.0 0.357142857143} test textDisp-14.9 {TkTextXviewCmd procedure} { .t delete 1.0 end .t insert end xxxxxxxxx\n @@ -1514,7 +1522,7 @@ test textDisp-14.9 {TkTextXviewCmd procedure} { .t insert end "xxxx xxxxxxxxx xxxxxxxxxxxxx" .t xview m 1.4 .t xview -} {0.642857 1} +} {0.642857142857 1.0} test textDisp-14.10 {TkTextXviewCmd procedure} { list [catch {.t xview scroll a} msg] $msg } {1 {wrong # args: should be ".t xview scroll number units|pages"}} @@ -1619,13 +1627,13 @@ for {set i 2} {$i <= 200} {incr i} { .t tag add big 100.0 105.0 .t insert 151.end { has a lot of extra text, so that it wraps around on the screen several times over.} .t insert 153.end { also has enoug extra text to wrap.} -update +update ; .t count -update -ypixels 1.0 end test textDisp-16.1 {TkTextYviewCmd procedure} { .t yview 21.0 set x [.t yview] .t yview 1.0 - set x -} {0.1 0.15} + list [expr {int([lindex $x 0]*100)}] [expr {int ([lindex $x 1] * 100)}] +} {9 14} test textDisp-16.2 {TkTextYviewCmd procedure} { list [catch {.t yview 2 3} msg] $msg } {1 {unknown option "2": must be moveto or scroll}} @@ -1661,7 +1669,7 @@ test textDisp-16.10 {TkTextYviewCmd procedure, "moveto" option} { test textDisp-16.11 {TkTextYviewCmd procedure, "moveto" option} { .t yview moveto 0.5 .t index @0,0 -} {101.0} +} {103.0} test textDisp-16.12 {TkTextYviewCmd procedure, "moveto" option} { .t yview moveto -1 .t index @0,0 @@ -1673,19 +1681,19 @@ test textDisp-16.13 {TkTextYviewCmd procedure, "moveto" option} { test textDisp-16.14 {TkTextYviewCmd procedure, "moveto" option} { .t yview moveto .75 .t index @0,0 -} {151.0} +} {151.60} test textDisp-16.15 {TkTextYviewCmd procedure, "moveto" option} { .t yview moveto .752 .t index @0,0 -} {151.20} +} {151.60} test textDisp-16.16 {TkTextYviewCmd procedure, "moveto" option} { .t yview moveto .754 .t index @0,0 -} {151.60} +} {151.80} test textDisp-16.17 {TkTextYviewCmd procedure, "moveto" option} { .t yview moveto .755 .t index @0,0 -} {152.0} +} {151.80} test textDisp-16.18 {TkTextYviewCmd procedure, "moveto" roundoff} {fonts} { catch {destroy .top1} toplevel .top1 @@ -1702,12 +1710,15 @@ test textDisp-16.18 {TkTextYviewCmd procedure, "moveto" roundoff} {fonts} { } {0.333333 0.833333} test textDisp-16.19 {TkTextYviewCmd procedure, "scroll" option} { list [catch {.t yview scroll a} msg] $msg -} {1 {wrong # args: should be ".t yview scroll number units|pages"}} +} {1 {wrong # args: should be ".t yview scroll number units|pages|pixels"}} test textDisp-16.20 {TkTextYviewCmd procedure, "scroll" option} { list [catch {.t yview scroll a b c} msg] $msg -} {1 {wrong # args: should be ".t yview scroll number units|pages"}} +} {1 {wrong # args: should be ".t yview scroll number units|pages|pixels"}} test textDisp-16.21 {TkTextYviewCmd procedure, "scroll" option} { list [catch {.t yview scroll badInt bogus} msg] $msg +} {1 {bad argument "bogus": must be units, pages or pixels}} +test textDisp-16.21.2 {TkTextYviewCmd procedure, "scroll" option} { + list [catch {.t yview scroll badInt units} msg] $msg } {1 {expected integer but got "badInt"}} test textDisp-16.22 {TkTextYviewCmd procedure, "scroll" option, back pages} { .t yview 50.0 @@ -1715,16 +1726,19 @@ test textDisp-16.22 {TkTextYviewCmd procedure, "scroll" option, back pages} { .t yview scroll -1 pages .t index @0,0 } {42.0} +test textDisp-16.22.1 {TkTextYviewCmd procedure, "scroll" option, back pages} { + list [catch {.t yview scroll -3 p} res] $res +} {1 {ambiguous argument "p": must be units, pages or pixels}} test textDisp-16.23 {TkTextYviewCmd procedure, "scroll" option, back pages} { .t yview 50.0 update - .t yview scroll -3 p + .t yview scroll -3 pa .t index @0,0 } {26.0} test textDisp-16.24 {TkTextYviewCmd procedure, "scroll" option, back pages} { .t yview 5.0 update - .t yview scroll -3 p + .t yview scroll -3 pa .t index @0,0 } {1.0} test textDisp-16.25 {TkTextYviewCmd procedure, "scroll" option, back pages} { @@ -1781,10 +1795,65 @@ test textDisp-16.31 {TkTextYviewCmd procedure, "scroll units" option} { } {151.40} test textDisp-16.32 {TkTextYviewCmd procedure} { list [catch {.t yview scroll 12 bogoids} msg] $msg -} {1 {bad argument "bogoids": must be units or pages}} +} {1 {bad argument "bogoids": must be units, pages or pixels}} test textDisp-16.33 {TkTextYviewCmd procedure} { list [catch {.t yview bad_arg 1 2} msg] $msg } {1 {unknown option "bad_arg": must be moveto or scroll}} +test textDisp-16.34 {TkTextYviewCmd procedure} { + set res {} + .t yview 1.0 + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 1 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 1 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 1 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 1 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 1 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] +} {0.0 1.0 2.0 3.0 4.0 5.0} +test textDisp-16.35 {TkTextYviewCmd procedure} { + set res {} + .t yview 1.0 + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 13 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll -4 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll -9 pixels + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] +} {0.0 13.0 9.0 0.0} +test textDisp-16.36 {TkTextYviewCmd procedure} { + set res {} + .t yview 1.0 + .t yview scroll 5 pixels + .t yview scroll -1 pages + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] + .t yview scroll 5 pixels + .t yview scroll -1 units + lappend res [expr {[lindex [.t yview] 0] * [.t count -ypixels 1.0 end]}] +} {0.0 0.0} +test textDisp-16.37 {TkTextYviewCmd procedure} { + list [catch {.t yview scroll 1.3 pixels} msg] $msg +} {0 {}} +test textDisp-16.38 {TkTextYviewCmd procedure} { + list [catch {.t yview scroll 1.3blah pixels} msg] $msg +} {1 {bad screen distance "1.3blah"}} +test textDisp-16.39 {TkTextYviewCmd procedure} { + list [catch {.t yview scroll 1.3i pixels} msg] $msg +} {0 {}} +test textDisp-16.40 {text count -xpixels} { + set res {} + lappend res [.t count -xpixels 1.0 1.5] \ + [.t count -xpixels 1.5 1.0] \ + [.t count -xpixels 1.0 13.0] \ + [.t count -xpixels 1.0 "1.0 displaylineend"] \ + [.t count -xpixels 1.0 "1.0 lineend"] \ + [.t count -xpixels 1.0 "1.0 displaylineend"] \ + [.t count -xpixels 1.0 end] +} {35 -35 0 42 42 42 0} .t delete 1.0 end foreach i {a b c d e f g h i j k l m n o p q r s t u v w x y z} { @@ -1861,7 +1930,7 @@ test textDisp-18.1 {GetXView procedure} { .t insert end xxxxxxxxxxxxxxxxxxxxxxxxxx update set scrollInfo -} {0 0.363636} +} {0.0 0.363636363636} test textDisp-18.2 {GetXView procedure} { .t configure -wrap char .t delete 1.0 end @@ -1870,13 +1939,13 @@ test textDisp-18.2 {GetXView procedure} { .t insert end xxxxxxxxxxxxxxxxxxxxxxxxxx update set scrollInfo -} {0 1} +} {0.0 1.0} test textDisp-18.3 {GetXView procedure} { .t configure -wrap none .t delete 1.0 end update set scrollInfo -} {0 1} +} {0.0 1.0} test textDisp-18.4 {GetXView procedure} { .t configure -wrap none .t delete 1.0 end @@ -1885,7 +1954,7 @@ test textDisp-18.4 {GetXView procedure} { .t insert end xxxxxxxxxxxxxxxxx update set scrollInfo -} {0 1} +} {0.0 1.0} test textDisp-18.5 {GetXView procedure} { .t configure -wrap none .t delete 1.0 end @@ -1895,7 +1964,7 @@ test textDisp-18.5 {GetXView procedure} { .t xview scroll 31 units update set scrollInfo -} {0.563636 0.927273} +} {0.563636363636 0.927272727273} test textDisp-18.6 {GetXView procedure} { .t configure -wrap none .t delete 1.0 end @@ -1916,7 +1985,7 @@ test textDisp-18.6 {GetXView procedure} { .t configure -wrap none update lappend x $scrollInfo -} {{0.553571 0.910714} {0 1} {0 1} {0 0.357143}} +} {{0.553571428571 0.910714285714} {0.0 1.0} {0.0 1.0} {0.0 0.357142857143}} test textDisp-18.7 {GetXView procedure} { .t configure -wrap none .t delete 1.0 end @@ -1948,7 +2017,7 @@ test textDisp-18.8 {GetXView procedure} { "error "scrolling error"" (procedure "scrollError" line 2) invoked from within -"scrollError 0 1" +"scrollError 0.0 1.0" (horizontal scrolling command executed by text)}} catch {rename bgerror {}} catch {rename bogus {}} @@ -1960,7 +2029,7 @@ test textDisp-19.1 {GetYView procedure} { .t delete 1.0 end update set scrollInfo -} {0 1} +} {0.0 1.0} test textDisp-19.2 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -1973,7 +2042,7 @@ test textDisp-19.2 {GetYView procedure} { test textDisp-19.3 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end - update + update; after 10 ; update set scrollInfo "unchanged" .t insert 1.0 "Line 1\nLine 2 is so long that it wraps around\nLine 3" update @@ -1990,7 +2059,7 @@ test textDisp-19.4 {GetYView procedure} { } update set scrollInfo -} {0 0.769231} +} {0.0 0.769230769231} test textDisp-19.5 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -1999,9 +2068,9 @@ test textDisp-19.5 {GetYView procedure} { .t insert end "\nLine $i" } .t insert 2.end " is really quite long; in fact it's so long that it wraps three times" - update + update ; after 100 set x $scrollInfo -} {0 0.538462} +} {0.0 0.625} test textDisp-19.6 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -2013,7 +2082,7 @@ test textDisp-19.6 {GetYView procedure} { .t yview 4.0 update set x $scrollInfo -} {0.230769 1} +} {0.375 1.0} test textDisp-19.7 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -2023,9 +2092,9 @@ test textDisp-19.7 {GetYView procedure} { } .t insert 2.end " is really quite long; in fact it's so long that it wraps three times" .t yview 2.26 - update + update; after 1; update set x $scrollInfo -} {0.097166 0.692308} +} {0.125 0.75} test textDisp-19.8 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -2036,8 +2105,9 @@ test textDisp-19.8 {GetYView procedure} { .t insert 10.end " is really quite long; in fact it's so long that it wraps three times" .t yview 2.0 update + .t count -update -ypixels 1.0 end set x $scrollInfo -} {0.0769231 0.732268} +} {0.0625 0.6875} test textDisp-19.9 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -2048,7 +2118,7 @@ test textDisp-19.9 {GetYView procedure} { .t yview 3.0 update set scrollInfo -} {0.133333 0.8} +} {0.133333333333 0.8} test textDisp-19.10 {GetYView procedure} { .t configure -wrap char .t delete 1.0 end @@ -2059,7 +2129,29 @@ test textDisp-19.10 {GetYView procedure} { .t yview 11.0 update set scrollInfo -} {0.333333 1} +} {0.333333333333 1.0} +test textDisp-19.10.1 {Widget manipulation causes height miscount} { + .t configure -wrap char + .t delete 1.0 end + .t insert 1.0 "Line 1" + foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} { + .t insert end "\nLine $i" + } + .t yview 11.0 + update + .t configure -wrap word + .t delete 1.0 end + .t insert 1.0 "Line 1" + foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} { + .t insert end "\nLine $i" + } + .t insert end "\nThis last line wraps around four " + .t insert end "times with a bit left on the last line." + .t yview insert + update + .t count -update -ypixels 1.0 end + set scrollInfo +} {0.5 1.0} test textDisp-19.11 {GetYView procedure} { .t configure -wrap word .t delete 1.0 end @@ -2071,8 +2163,134 @@ test textDisp-19.11 {GetYView procedure} { .t insert end "times with a bit left on the last line." .t yview insert update + .t count -update -ypixels 1.0 end set scrollInfo -} {0.625 1} +} {0.5 1.0} +test textDisp-19.11.2 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 1.0 end +} {20} +test textDisp-19.11.3 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines end 1.0 +} {-20} +test textDisp-19.11.4 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 1.1 1.3 +} {0} +test textDisp-19.11.5 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 16.1 +} {0} +test textDisp-19.11.5.1 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 16.5 +} {0} +test textDisp-19.11.6 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 16.20 +} {1} +test textDisp-19.11.7 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 16.40 +} {2} +test textDisp-19.11.8 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines "16.0 displaylineend +1c" "16.0 lineend" +} {3} +test textDisp-19.11.9 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 "16.0 lineend" +} {4} +test textDisp-19.11.10 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 "16.0 +4displaylines" +} {4} +test textDisp-19.11.11 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines 16.0 "16.0 +2displaylines" +} {2} +test textDisp-19.11.12 {TextWidgetCmd procedure, "count -displaylines"} { + .t count -displaylines "16.0 +1displayline" "16.0 +2displaylines -1c" +} {0} +.t tag configure elide -elide 1 +test textDisp-19.11.13 {TextWidgetCmd procedure, "count -displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "16.0 +1displaylines" "16.0 +1displaylines +6c" + .t count -displaylines 16.0 "16.0 +4displaylines" +} {4} +test textDisp-19.11.14 {TextWidgetCmd procedure, "count -displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "16.0 +1displaylines" "16.0 +1displaylines displaylineend" + .t count -displaylines 16.0 "16.0 +4displaylines" +} {4} +test textDisp-19.11.15 {TextWidgetCmd procedure, "count -displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "16.0 +1displaylines" "16.0 +2displaylines" + .t count -displaylines 16.0 "16.0 +4displaylines -1c" +} {3} +test textDisp-19.11.15 {TextWidgetCmd procedure, "count -displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "16.0 +1displaylines" "16.0 +2displaylines" + .t count -displaylines 16.0 "16.0 +4displaylines" +} {4} +test textDisp-19.11.16 {TextWidgetCmd procedure, "count -displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.0" "14.0" + .t count -displaylines 12.0 16.0 +} {2} +test textDisp-19.11.17 {TextWidgetCmd procedure, "index +displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.0" "14.0" + list [.t index "11.5 +2d lines"] \ + [.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \ + [.t index "13.0 +2d lines"] [.t index "13.1 +3d lines"] \ + [.t index "13.0 +4d lines"] +} {15.5 16.0 15.0 16.0 16.15 16.33} +test textDisp-19.11.18 {TextWidgetCmd procedure, "index +displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.0" "14.0" + list [.t index "15.5 -2d lines"] \ + [.t index "16.0 -2d lines"] [.t index "15.0 -2d lines"] \ + [.t index "16.0 -3d lines"] [.t index "16.17 -4d lines"] \ + [.t index "16.36 -5d lines"] +} {11.5 14.0 11.0 11.0 11.2 11.3} +test textDisp-19.11.19 {TextWidgetCmd procedure, "count -displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.0" "16.0 +1displaylines" + .t count -displaylines 12.0 17.0 +} {4} +test textDisp-19.11.20 {TextWidgetCmd procedure, "index +displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.0" "16.0 +1displaylines" + list [.t index "11.5 +2d lines"] \ + [.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \ + [.t index "13.0 +2d lines"] [.t index "13.0 +3d lines"] \ + [.t index "13.0 +4d lines"] +} {16.38 16.50 16.33 16.50 16.67 17.0} +test textDisp-19.11.21 {TextWidgetCmd procedure, "index +displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.0" "16.0 +1displaylines" + list [.t index "16.38 -2d lines"] \ + [.t index "16.50 -3d lines"] [.t index "16.33 -2d lines"] \ + [.t index "16.53 -4d lines"] [.t index "16.69 -4d lines"] \ + [.t index "17.1 -5d lines"] +} {11.5 11.0 11.0 10.3 11.2 11.0} +test textDisp-19.11.22 {TextWidgetCmd procedure, "index +displaylines"} { + .t tag remove elide 1.0 end + list [.t index "end +5d lines"] \ + [.t index "end -3d lines"] [.t index "1.0 -2d lines"] \ + [.t index "1.0 +4d lines"] [.t index "1.0 +50d lines"] \ + [.t index "end -50d lines"] +} {17.0 16.33 1.0 5.0 17.0 1.0} +test textDisp-19.11.23 {TextWidgetCmd procedure, "index +displaylines"} { + .t tag remove elide 1.0 end + .t tag add elide "12.3" "16.0 +1displaylines" + list [.t index "11.5 +2d lines"] \ + [.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \ + [.t index "13.0 +2d lines"] [.t index "13.0 +3d lines"] \ + [.t index "13.0 +4d lines"] +} {16.21 16.33 16.16 16.50 16.67 17.0} +.t tag remove elide 1.0 end +test textDisp-19.11.24 {TextWidgetCmd procedure, "index +/-displaylines"} { + list [.t index "11.5 + -1 display lines"] \ + [.t index "11.5 + +1 disp lines"] \ + [.t index "11.5 - -1 disp lines"] \ + [.t index "11.5 - +1 disp lines"] \ + [.t index "11.5 -1 disp lines"] \ + [.t index "11.5 +1 disp lines"] \ + [.t index "11.5 +0 disp lines"] +} {10.5 12.5 12.5 10.5 10.5 12.5 11.5} +.t tag remove elide 1.0 end test textDisp-19.12 {GetYView procedure, partially visible last line} { catch {destroy .top} toplevel .top @@ -2080,12 +2298,13 @@ test textDisp-19.12 {GetYView procedure, partially visible last line} { text .top.t -width 40 -height 5 pack .top.t -expand yes -fill both .top.t insert end "Line 1\nLine 2\nLine 3\nLine 4\nLine 5" - update + # Need to wait for asychronous calculations to complete. + update ; after 10 scan [wm geom .top] %dx%d twidth theight wm geom .top ${twidth}x[expr $theight - 3] update .top.t yview -} {0 0.8} +} {0.0 0.9625} test textDisp-19.13 {GetYView procedure, partially visible last line} {fonts} { catch {destroy .top} toplevel .top @@ -2109,7 +2328,8 @@ test textDisp-19.14 {GetYView procedure} { } .t insert end "\nThis last line wraps around four " .t insert end "times with a bit left on the last line." - update + # Need to update so everything is calculated. + update ; .t count -update -ypixels 1.0 end set scrollInfo "unchanged" .t mark set insert 3.0 .t tag configure x -background red @@ -2143,9 +2363,30 @@ test textDisp-19.15 {GetYView procedure} { "error "scrolling error"" (procedure "scrollError" line 2) invoked from within -"scrollError 0 1" +"scrollError 0.0 1.0" (vertical scrolling command executed by text)} NONE} +test textDisp-19.16 {count -ypixels} { + .t configure -wrap word + .t delete 1.0 end + .t insert 1.0 "Line 1" + foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} { + .t insert end "\nLine $i" + } + .t insert end "\nThis last line wraps around four " + .t insert end "times with a bit left on the last line." + # Need to update so everything is calculated. + update + set res {} + lappend res \ + [.t count -ypixels 1.0 end] \ + [.t count -update -ypixels 1.0 end] \ + [.t count -ypixels 15.0 16.0] \ + [.t count -ypixels 15.0 "16.0 displaylineend +1c"] \ + [.t count -ypixels 16.0 "16.0 displaylineend +1c"] \ + [.t count -ypixels "16.0 +1 displaylines" "16.0 +4 displaylines +3c"] +} {300 300 15 30 15 45} + .t delete 1.0 end .t insert end "Line 1" for {set i 2} {$i <= 200} {incr i} { @@ -2862,6 +3103,127 @@ test textDisp-29.3 {miscellaneous: lines wrap but are still too long} {fonts} { list [.t2.t xview] [winfo geom .t2.t.f] [.t2.t bbox 1.3] } {{0.536667 1} 300x50+-156+18 {}} +test textDisp-30.1 {elidden text complications} {knownBug} { + .t2.t delete 1.0 end + .t2.t insert 1.0 "1111\n2222\n3333" + .t2.t tag configure elidden -elide 1 -background red + .t2.t tag add elidden 1.2 3.2 + # Known Bug: the newline at 1.4 will not be elidden. + # Each logical line must have its own DLines + .t2.t count -displaylines 1.0 end +} {1} + +.t configure -height 1 +update + +test textDisp-31.1 {line embedded window height update} { + set res {} + .t delete 1.0 end + .t insert end "abcd\nefgh\nijkl\nmnop\nqrst\nuvwx\nyx" + frame .t.f -background red -width 100 -height 100 + .t window create 3.0 -window .t.f + lappend res [.t count -update -ypixels 1.0 end] + .t.f configure -height 10 + lappend res [.t count -ypixels 1.0 end] + lappend res [.t count -update -ypixels 1.0 end] + set res +} {190 190 105} + +test textDisp-31.2 {line update index shifting} { + set res {} + .t.f configure -height 100 + update + lappend res [.t count -update -ypixels 1.0 end] + .t.f configure -height 10 + .t insert 1.0 "abc\n" + .t insert 1.0 "abc\n" + lappend res [.t count -ypixels 1.0 end] + lappend res [.t count -update -ypixels 1.0 end] + .t.f configure -height 100 + .t delete 1.0 3.0 + lappend res [.t count -ypixels 1.0 end] + lappend res [.t count -update -ypixels 1.0 end] + set res +} {190 220 135 105 190} + +test textDisp-31.3 {line update index shifting} { + # Should do exactly the same as the above, as long + # as we are correctly tagging the correct lines for + # recalculation. The 'update' and 'delay' must be + # long enough to ensure all asynchronous updates + # have been performed. + set res {} + .t.f configure -height 100 + update + lappend res [.t count -update -ypixels 1.0 end] + .t.f configure -height 10 + .t insert 1.0 "abc\n" + .t insert 1.0 "abc\n" + lappend res [.t count -ypixels 1.0 end] + update ; after 1000 ; update + lappend res [.t count -ypixels 1.0 end] + .t.f configure -height 100 + .t delete 1.0 3.0 + lappend res [.t count -ypixels 1.0 end] + update ; after 1000 ; update + lappend res [.t count -ypixels 1.0 end] + set res +} {190 220 135 105 190} + +test textDisp-31.4 {line embedded image height update} { + set res {} + image create photo textest -height 100 -width 10 + .t delete 3.0 + .t image create 3.0 -image textest + update + lappend res [.t count -update -ypixels 1.0 end] + textest configure -height 10 + lappend res [.t count -ypixels 1.0 end] + lappend res [.t count -update -ypixels 1.0 end] + set res +} {190 190 105} + +test textDisp-31.5 {line update index shifting} { + set res {} + textest configure -height 100 + update ; after 1000 ; update + lappend res [.t count -update -ypixels 1.0 end] + textest configure -height 10 + .t insert 1.0 "abc\n" + .t insert 1.0 "abc\n" + lappend res [.t count -ypixels 1.0 end] + lappend res [.t count -update -ypixels 1.0 end] + textest configure -height 100 + .t delete 1.0 3.0 + lappend res [.t count -ypixels 1.0 end] + lappend res [.t count -update -ypixels 1.0 end] + set res +} {190 220 135 105 190} + +test textDisp-31.6 {line update index shifting} { + # Should do exactly the same as the above, as long + # as we are correctly tagging the correct lines for + # recalculation. The 'update' and 'delay' must be + # long enough to ensure all asynchronous updates + # have been performed. + set res {} + textest configure -height 100 + update ; after 1000 ; update + lappend res [.t count -update -ypixels 1.0 end] + textest configure -height 10 + .t insert 1.0 "abc\n" + .t insert 1.0 "abc\n" + lappend res [.t count -ypixels 1.0 end] + update ; after 1000 ; update + lappend res [.t count -ypixels 1.0 end] + textest configure -height 100 + .t delete 1.0 3.0 + lappend res [.t count -ypixels 1.0 end] + update ; after 1000 ; update + lappend res [.t count -ypixels 1.0 end] + set res +} {190 220 135 105 190} + deleteWindows option clear diff --git a/tests/textImage.test b/tests/textImage.test index 30cd6b7..3436875 100644 --- a/tests/textImage.test +++ b/tests/textImage.test @@ -7,7 +7,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: textImage.test,v 1.7 2003/05/19 21:19:52 dkf Exp $ +# RCS: @(#) $Id: textImage.test,v 1.8 2003/10/31 09:02:16 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv @@ -143,7 +143,7 @@ test textImage-1.15 {align argument checking} { text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0 pack .t list [catch {.t image create end -image small -align wrong} msg] $msg -} {1 {bad alignment "wrong": must be baseline, bottom, center, or top}} +} {1 {bad align "wrong": must be baseline, bottom, center, or top}} test textImage-1.16 {configure} { catch { @@ -302,7 +302,9 @@ test textImage-4.2 {alignment checking - baseline} { .t image create end -image small -align baseline .t insert end test set result "" - foreach size {10 15 20 30} { + # Sizes larger than 25 can be too big and lead to a negative 'norm', + # at least on Windows XP with certain settings. + foreach size {10 15 20 25} { font configure test_font2 -size $size array set Metrics [font metrics test_font2] update @@ -316,7 +318,7 @@ test textImage-4.2 {alignment checking - baseline} { font delete test_font2 unset Metrics set result -} {{10 0} {15 0} {20 0} {30 0}} +} {{10 0} {15 0} {20 0} {25 0}} test textImage-4.3 {alignment and padding checking} {fonts} { catch { diff --git a/tests/textIndex.test b/tests/textIndex.test index 0f9a468..1c53b17 100644 --- a/tests/textIndex.test +++ b/tests/textIndex.test @@ -6,7 +6,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: textIndex.test,v 1.9 2003/05/19 13:04:24 vincentdarley Exp $ +# RCS: @(#) $Id: textIndex.test,v 1.10 2003/10/31 09:02:17 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv @@ -695,6 +695,96 @@ test testIndex-18.1 {Object indices don't cache mark names} { set res } {3.4 3.0 1.0} +frame .f -width 100 -height 20 +pack append . .f left + +set fixedFont {Courier -12} +set fixedHeight [font metrics $fixedFont -linespace] +set fixedWidth [font measure $fixedFont m] + +set varFont {Times -14} +set bigFont {Helvetica -24} +destroy .t +text .t -font $fixedFont -width 20 -height 10 -wrap char +pack append . .t {top expand fill} +.t tag configure big -font $bigFont +.t debug on +wm geometry . {} + +# The statements below reset the main window; it's needed if the window +# manager is mwm to make mwm forget about a previous minimum size setting. + +wm withdraw . +wm minsize . 1 1 +wm positionfrom . user +wm deiconify . +update + +# Some window managers (like olwm under SunOS 4.1.3) misbehave in a way +# that tends to march windows off the top and left of the screen. If +# this happens, some tests will fail because parts of the window will +# not need to be displayed (because they're off-screen). To keep this +# from happening, move the window if it's getting near the left or top +# edges of the screen. + +if {([winfo rooty .] < 50) || ([winfo rootx .] < 50)} { + wm geom . +50+50 +} + +set str [string repeat "hello " 20] + +.t insert end "$str one two three four five six seven height nine ten\n" +.t insert end "$str one two three four five six seven height nine ten\n" +.t insert end "$str one two three four five six seven height nine ten\n" + +test testIndex-19.1 {Display lines} { + .t index "2.7 displaylinestart" +} {2.0} + +test testIndex-19.2 {Display lines} { + .t index "2.7 displaylineend" +} {2.19} + +test testIndex-19.3 {Display lines} { + .t index "2.30 displaylinestart" +} {2.20} + +test testIndex-19.4 {Display lines} { + .t index "2.30 displaylineend" +} {2.39} + +test testIndex-19.5 {Display lines} { + .t index "2.40 displaylinestart" +} {2.40} + +test testIndex-19.6 {Display lines} { + .t index "2.40 displaylineend" +} {2.59} + +test testIndex-19.7 {Display lines} { + .t index "2.7 +1displaylines" +} {2.27} + +test testIndex-19.8 {Display lines} { + .t index "2.7 -1displaylines" +} {1.167} + +test testIndex-19.9 {Display lines} { + .t index "2.30 +1displaylines" +} {2.50} + +test testIndex-19.10 {Display lines} { + .t index "2.30 -1displaylines" +} {2.10} + +test testIndex-19.11 {Display lines} { + .t index "2.40 +1displaylines" +} {2.60} + +test testIndex-19.12 {Display lines} { + .t index "2.40 -1displaylines" +} {2.20} + # cleanup rename textimage {} catch {destroy .t} diff --git a/tests/textWind.test b/tests/textWind.test index ea60b89..6d60d54 100644 --- a/tests/textWind.test +++ b/tests/textWind.test @@ -6,7 +6,7 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: textWind.test,v 1.8 2003/09/30 08:45:46 patthoyts Exp $ +# RCS: @(#) $Id: textWind.test,v 1.9 2003/10/31 09:02:17 vincentdarley Exp $ package require tcltest 2.1 eval tcltest::configure $argv @@ -630,7 +630,7 @@ test textWind-13.1 {EmbWinBboxProc procedure} { update list [winfo geom .f] [.t bbox .f] } {5x5+21+6 {21 6 5 5}} -test textWind-13.2 {EmbWinBboxProc procedure} { +test textWind-13.2 {EmbWinBboxProc procedure} {fonts} { .t delete 1.0 end .t insert 1.0 "Some sample text" frame .f -width 5 -height 5 -bg $color @@ -686,7 +686,7 @@ test textWind-13.8 {EmbWinBboxProc procedure} {fonts} { update list [winfo geom .f] [.t bbox .f] } {5x11+21+6 {21 6 5 11}} -test textWind-13.9 {EmbWinBboxProc procedure, spacing options} { +test textWind-13.9 {EmbWinBboxProc procedure, spacing options} {fonts} { .t configure -spacing1 5 -spacing3 2 .t delete 1.0 end .t insert 1.0 "Some sample text" @@ -736,7 +736,7 @@ test textWind-14.3 {EmbWinDelayedUnmap procedure} { update .t yview 2.0 set result [winfo ismapped .f] - update + update ; after 10 list $result [winfo ismapped .f] } {1 0} test textWind-14.4 {EmbWinDelayedUnmap procedure} { diff --git a/unix/tkUnixDefault.h b/unix/tkUnixDefault.h index 84f6672..e8bbea7 100644 --- a/unix/tkUnixDefault.h +++ b/unix/tkUnixDefault.h @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkUnixDefault.h,v 1.18 2003/08/14 10:31:14 dkf Exp $ + * RCS: @(#) $Id: tkUnixDefault.h,v 1.19 2003/10/31 09:02:17 vincentdarley Exp $ */ #ifndef _TKUNIXDEFAULT @@ -479,6 +479,7 @@ #define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR NORMAL_BG #define DEF_TEXT_BG_MONO WHITE +#define DEF_TEXT_BLOCK_CURSOR "0" #define DEF_TEXT_BORDER_WIDTH "2" #define DEF_TEXT_CURSOR "xterm" #define DEF_TEXT_FG BLACK diff --git a/win/tkWinDefault.h b/win/tkWinDefault.h index 66c1ae4..51bb74c 100644 --- a/win/tkWinDefault.h +++ b/win/tkWinDefault.h @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkWinDefault.h,v 1.16 2002/06/21 23:09:54 hobbs Exp $ + * RCS: @(#) $Id: tkWinDefault.h,v 1.17 2003/10/31 09:02:18 vincentdarley Exp $ */ #ifndef _TKWINDEFAULT @@ -466,6 +466,7 @@ #define DEF_TEXT_AUTO_SEPARATORS "1" #define DEF_TEXT_BG_COLOR "SystemWindow" #define DEF_TEXT_BG_MONO WHITE +#define DEF_TEXT_BLOCK_CURSOR "0" #define DEF_TEXT_BORDER_WIDTH "2" #define DEF_TEXT_CURSOR "xterm" #define DEF_TEXT_FG TEXT_FG -- cgit v0.12