Tk Source Code

View Ticket
Login
Ticket UUID: b461c703993375d4a0498b11b71db814c91ad375
Title: text widget handling of the empty space left from line wrap
Type: Bug Version: 8.6.11, revised_text
Submitter: anonymous Created on: 2021-04-19 23:55:35
Subsystem: 75. wish Assigned To: fvogel
Priority: 5 Medium Severity: Important
Status: Closed Last Modified: 2022-12-26 10:05:57
Resolution: Fixed Closed By: fvogel
    Closed on: 2022-12-26 10:05:57
Description:

The user's intention is to place the insert mark at the end of a wrapped line.
Naturally the user clicks in the empty space remaining to the right of the last
visible character of that line. Depending on where the click happens, there are
two outcomes:

  1. A click to the left of the center of the empty space places the insert mark
    at the end of the last character of that line, as expected.
  2. A click to the right of the center places the insert mark at the beginning
    of the next line, which was not intended nor expected.
I checked LibreOffice Writer, Kubuntu's Kate and KWrite, Mate's Pluma, and Xfce's
Mousepad, and they all place the insert mark at the end of the last character
of the same line, not at the start of the next line.

To replicate this behavior:

package require Tk
pack [text .t -width 10 -height 3 -font {-size 72} -wrap word] -fill both -expand 1
.t insert end "123456 1234567"
bind .t <ButtonRelease-1> {puts bbox\t[%W bbox [%W index insert]]}
Click in the first line, at the far right side of that line's empty space. The insert mark
goes to the start of the next line. Apparently the center-line principle (see this ticket too)
is being applied to the empty space now representing the space character where the wrapping
occurs, only the choice forced here is clearly not "the closest edge of the character underneath
the mouse cursor".

More complications from applying the centerline principle to the empty space left from wrapping:
  • Double click in the empty space left by the wrapped line, to the left of the virtual centerline.
    The long empty space gets selected, presumably corresponding to the space character facilitating
    the line wrap.
  • Double click to the right of the centerline: the first word of the next line gets selected,
    which is unexpected.
  • Play with the window's width in order to change line wrapping; place the insert mark at
    the end of the word before the wrap, and watch the bbox dimensions printed for
    the space character facilitating the line wrap; the metrics differ significantly:
    the regular space (i.e. when not on the wrap edge) has the dimensions of the space char of
    the font it belongs to, while the wrapped space varies greatly, depending on the size of
    the empty space left from the line wrap. While having a variable space char may be useful in
    fully justifying text lines, such behavior is undocumented and unexpected.
Checking how other editors behave when double clicking in the empty space left from wrapping:
  • LibreOffice Writer selects the preceding word, while the space char which facilitated
    the wrap is not visible in the wrapped line.
  • Kate and KWrite select the previous word, the space char is visible but not selected,
    and on wrapped lines with long words, the empty space which precedes the wrapped word is
    also selected, but not the space char per se.
  • Pluma selects the space character itself, but, oddly, it doesn't highlight the selection.
  • Mousepad appears not to select anything.
dzach
User Comments: fvogel added on 2022-12-26 10:05:57:

I have merged the bugfix branch bug-b461c70399 into the revised_text branch (only). This changes the behavior when, in a -wrap !none mode, the user clicks in the right half of the blank area at the end of a display line (previously: the cursor got positioned at the beginning of the next display line, now it will be at the end of the current display line which is more in line with what current other editors do). Moreover, an attempt to better document dlineinfo is implemented.

After 18 months with no activity I'm closing this ticket now. If someone comes up with better documentation we can always make further commits. Thanks for the discussion!


anonymous added on 2021-06-06 15:18:11:
Hi fvogel. I'm sorry for my late response. 

1. The issue of placing the insert mark in the space left from a wrapped line 
seems to be solved.
2. The concept of a variable space and the reason why dlineinfo and bbox return 
different values for the space left by a wrapped line is, in my view, still not 
explained adequately or solved.

But I feel I've been enough of a nuisance on this matter and I'd better stop 
here.

Thank you for looking into this report and for all the work you do for the text 
widget.

dzach

fvogel added on 2021-05-15 09:07:21:

Revisiting this issue, I think we can change the behavior at clicking on a wrapped line end in the revised_text easily, but less easily in the legacy widget. I did this in [515c759bf5], which also adds a piece of documentation about what dlineinfo returns when -wrap is different from 'none'.

Would you be OK with this? If so, I would backport the man page part of the above commit to the legacy widget.


anonymous added on 2021-05-03 21:05:04:

Hi fvogel

> First thing, the space at the end of line is a single space, it's not a 
> "regular" space followed by a variable width space that is present of not 
> depending on the justification setting. There is a single space character (from 
> the chars inserted in the widget), and it may have variable width depending on 
> justification, wrapping, location on the line, fixed/not fixed font used, and 
> whatnot (but not -rmargin, I have checked).

When you run the code I posted in the previous post, and vary the width of the window,
you will see dlineinfo returning the visible space of the line, including the 
portion of the fixed space that is visible. As soon as the width extends beyond the 
fixed space, the behaviors of bbox and dlineinfo deviate; bbox width extends to include the
flexible space while dlineinfo does not. This is what I interpreted as the existence of
both a fixed and a variable width space at the same time.

> Please re-read the man page for bbox, I believe it's clear there.

It is clear, provided there exists a notion of a 'flexible space character' and its behavior,
which does not.

> It is true that when wrapping it does not return the full width of the window but my opinion 
> is that this is a feature.

In that case, there seems to be a need to refine the definition of a display line to include
the fixed space and exclude the flexible space left when the line breaks.

> [...] It gives a different information than the sum of the bboxes of all chars in that display 
> line, and it's the only way to get that information.

One can get the same information from a dlineinfo which would return a full line's width from
left to right, meaning the sum of all character bboxes with the same y coordinate, and then
subtract the width of the flexible space returned by its bbox. In this case both bbox and 
dlineinfo would include a flexible and/or a non flexible space at the end of the line, whether it
wraps or not.

> Agreed, that could be clarified in the man page, which only says what's happening when 
> wrapping is "none" but not when it's something else.

But I seem to be failing to describe it properly, each time. 

:-)


fvogel added on 2021-05-02 08:18:23:
Well, bbox and dline subcommands are two different beasts.

First thing, the space at the end of line is a single space, it's not a "regular" space followed by a variable width space that is present of not depending on the justification setting. There is a single space character (from the chars inserted in the widget), and it may have variable width depending on justification, wrapping, location on the line, fixed/not fixed font used, and whatnot (but not -rmargin, I have checked).

Second, bbox always measures the area occupied by a character. When you request the bbox of the space character you get its x, y, width and heigth according to all the above mentioned parameters. You get its dimensions as displayed on the screen. Please re-read the man page for bbox, I believe it's clear there.

Third, dlineinfo deals with display lines. It measures position and size of the display line containing the index it is been given. It is true that when wrapping it does not return the full width of the window but my opinion is that this is a feature. It gives a different information than the sum of the bboxes of all chars in that display line, and it's the only way to get that information. Agreed, that could be clarified in the man page, which only says what's happening when wrapping is "none" but not when it's something else.

anonymous added on 2021-04-25 01:25:32:

I see your point with the patch. However that is true with every long standing
buggy behavior, isn't it? But then again, maybe others don't see it as buggy behavior, 
because they've been accustomed to it. Kind of a chicken and egg situation.

Indeed, my definition was not accurate. The thing gets even more involved the
more one digs into it. The reason I propose to document the existence of a variable-width 
space is because it has implications on the measurements returned by dlineinfo and bbox:
  • when left or center justified, dlineinfo seems to be measuring the regular space character that facilitates the wrap, completely ignoring the existence of the variable-width space. In practical terms, the regular space is measured but cannot be selected, while the variable-width space is not measured but is the one that is selected, upon a selection.
  • when left or center justified, bbox seems to be completely ignoring the existence of the regular space and only reports the dimensions of the variable-width space.
  • when right justified, both dlineinfo and bbox seem to be measuring the regular space char which now re-appears, and is also the one that gets selected upon a selection.
Here is a demo of the above: package require Tk pack [text .t -width 6 -height 2 -font {-family monospace -size 72} -wrap word ] -fill both -expand 1 bind .t <Configure> {puts "bbox\t[lindex [.t bbox 1.6] 2]\tdline\t[lindex [.t dline 1.6] 2]"} .t insert end "012345 67890" If the widget is expanded horizontally, bbox increases continuously while dlineinfo stops increasing after the width exceeds that of the regular space. It seems to me that if both dlineinfo and bbox measured the regular space char, and the regular space char were the one that got selected upon a selection, then whether a variable-width space existed or not would have no practical implication to the user. As it stands now, and since the description of the variable-width space is not offering any practical help to the user, then maybe the discrepancies in the measurements returned by dlineinfo and bbox could be described in the KNOWN BUGS section? dzach


fvogel added on 2021-04-24 19:54:40:

Thanks for your proposal.

Two remarks:

  • About applying my patch: I'm not fully comfortable in doing so in the 8.6.x line of patch releases. This behavior we would be changing is a very long established one, in fact it's like this since the beginning of time, that is the beginning of version control history in 1998. I would prefer to change this in 8.7 only, and probably even in revised_text only.
  • Documentation of the flexible width space at the end of a wrapped line: I have to admit I'm not convinced there is a need, and moreover what would be the message we want to pass to the reader? What you are proposing is not accurate: this space is really not *elided* (this word has a precise meaning in the text widget realm), and it can be selectable by whatever means (not just double-clicking). What is the purpose of saying this space is of variable width? If we really want to say something, I don't think we can go further than "When a line is wrapped, the space character where the wrap occurs has variable width." (and even so: this is only true when -justify is not "left"). As you see it's not easy to document something accurately and correctly.


anonymous added on 2021-04-21 15:38:13:

Here is a suggestion for a change in the text of the manual, which includes
the behavior of the flexible space. Please feel free too change it to better reflect
the actual behavior:

    Command-Line Name: -wrap
    Database Name: wrap
    Database Class: Wrap
    Specifies how to handle lines in the text that are too long to be displayed in a single line of the text's window. The value must be none or char or word. A wrap mode of none means that each line of text appears as exactly one line on the screen; extra characters that do not fit on the screen are not displayed. In the other modes each line of text will be broken up into several screen lines if necessary to keep all the characters visible. In char mode a screen line break may occur after any character; in word mode a line break will only be made at word boundaries.

    When a line is word wrapped, the space character where the wrap occurs is converted to a flexible space filling the rest of the wrapped line. This flexible space is elided, but is still selectable by double clicking in the empty space remaining in the wrapped line.


anonymous added on 2021-04-21 11:59:31:
fvogel, I apologize, I think I misread your reply.
I just applied the patch you provided, and it solves points 1 and 2 of my listing.
Then, the only remaining issue is documenting the "flexible space" behavior in the text widget man page.

Thank you for the patch.

anonymous added on 2021-04-21 11:04:31:

> what behavior would you like to see when double clicking? It's unclear to me.

To answer the question, I would look into what a user reasonably expects when clicking
in the empty space left by the line wrap, which extends the same line as the text before
the wrap. Approaching it in such a way, I believe that:
  1. For a single click, the clear expectation is to place the insert marker at the end of the existing text in that line, no matter where in that line the click occurred.
  2. When double clicking in the empty space, the user is knowingly double clicking, i.e. selecting, NOT a word or a character, but a space, therefore the current behavior of selecting some space seems to be fulfilling the expectation. What is NOT expected is the selection of a distant word in the next line, which happens when the click occurs in the right half of the empty wrap space. I believe this too needs to be corrected.
  3. The appearance of a flexible space character is unique to wrapped text lines, and is not documented. So I believe this behavior needs to be documented. Possibly the flexible space concept could be extended to facilitate full text justification (already present in the revised text widget) and kerning.
The above are worth the time spent only if they are deemed beneficial to all users, and not just a solution for personal use. Hence my reporting of this as an issue. If a patch is expected, I'll try to provide one using the code in library/text.tcl, as you suggested. Thank you for looking into this. dzach


fvogel added on 2021-04-21 09:06:35:

Not sure how to deal with this report, especially since you report yourself that different editors behave differently. There seem to be no standard, therefore I'm not really seeing this report as a bug, except perhaps regarding the location of the insert mark on click at the end of the display line.

Also, apart from the insert index at the end of the clicked line, what behavior would you like to see when double clicking? It's unclear to me.

The behavior you describe for the Tk text widget is mostly implemented (in Tcl) in the file library/text.tcl. See proc tk::TextClosestGap in particular. You can experiment easily yourself, and try to sketch the behavior you want.

For instance, here is the start of a patch (against current core-8-6-branch) that might do part of what you want:

Index: library/text.tcl
==================================================================
--- library/text.tcl
+++ library/text.tcl
@@ -536,11 +536,12 @@
     set pos [$w index @$x,$y]
     set bbox [$w bbox $pos]
     if {$bbox eq ""} {
 	return $pos
     }
-    if {($x - [lindex $bbox 0]) < ([lindex $bbox 2]/2)} {
+    if {(($x - [lindex $bbox 0]) < ([lindex $bbox 2]/2))
+        || ([$w compare $pos == "$pos display lineend"] == 1)} {
 	return $pos
     }
     $w index "$pos + 1 char"
 }