2

Is there a more efficient way to get the total number of display lines (not just visible ones) that are left in a tkinter Text widget after the text insert than with this makeshift function I came up with (in Python 3.4.2):

def get_display_lines():
    #Gets the total number of display lines after the text insert in the current tab (not just those on-screen, though).
    myTextWidget.mark_set("dlines", INSERT)
    count=0
    while 1:
        if myTextWidget.compare("dlines", ">=", "end-1c linestart"):
            break
        else:
            myTextWidget.mark_set("dlines", "dlines + 1 display line")
            count+=1
    return count

Anyway, this code works, but it's rather inefficient. I'm using it to calculate the time left to autoscroll through the remainder document. The inefficiency causes problems when moving the text insert with the arrow keys (which calls this function so as to show a current accurate time). It makes moving the insert much slower, and slower to update, and can even crash/freeze it.

I know there are much more efficient ways to get the total number of lines in a Text widget. However, I need the display lines, because reading with autoscroll is usually done with the wordwrap on.

My autoscroll feature goes down from the insert a user-chosen number of lines every so many seconds (which number of second is also chosen by the user). It's not done through the scrollbars.

Brōtsyorfuzthrāx
  • 4,387
  • 4
  • 34
  • 56

2 Answers2

3

If you only care about the time to scroll through the document, and if the data doesn't change as you're scrolling (ie: new lines aren't being added) you can simply calculate how many pixels the document takes up (you only have to do this once), and where you currently are in the document. That will tell you how many pixels are left. From that you should be able to calculate an approximation of how much time it will take.

You can get the total number of pixels used in the text widget with myTextWidget.count("1.0", "end", "ypixels". You can then get the y coordinate of the current line with myTextWidget.dlineinfo("insert"). From those two numbers you can compute a percentage of how far the insertion point is from the end of the widget.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
2

Okay, I found an answer (and yes, this is purposefully an answer; it's not part of my question).

The following code will much more efficiently give the number of display lines left after the text insert:

def get_display_lines(start="insert", end="end-1c")
    try:
        count=myTextWidget.count(start, end, "displaylines")[0]
    except TypeError:
        count=0
    return count

Although this is much more efficient, it is still not totally efficient and will have trouble with large files (if you're using the arrow keys a lot during autoscroll). The count() method returns a tuple; hence, the zero index. Because this does not always contain a number (like when the insert is at the end of the document), that is why I use a try/except block (although you could do this lots of other ways; however you please).

Because this is not efficient for large files, you might just get the current lines left once, save them, and increment/decrement this variable when updating the status (granted, that will require editing a bunch of functions, but it is more efficient). You'll need to update the total during edits, though (fortunately, that is less common while autoscrolling).

Because messing around with a lot of other functions is a lot of overhead, and increases the complexity of editing the code, I suggest just only having it update on the autoscroll intervals, rather than when the user manually moves the cursor. Also, to increase efficiency, don't have it calculate the lines left (let alone the time left from that) in the statusbar update method, but only calculate it in a saved variable from the method called by the timer, and just print the variable in the statusbar.

Brōtsyorfuzthrāx
  • 4,387
  • 4
  • 34
  • 56
  • 1
    To improve the efficiency, when the user moves the cursor you can use `after` to update the interval in a second or so. Whenever they move the cursor you cancel that job and reschedule. That way, if they are moving the cursor fast you can defer the calculations until they pause for a short time. – Bryan Oakley Apr 03 '15 at 11:12
  • @BryanOakley `'displaylines'` is returning a number that seems to be more like the number of characters now, instead of number of lines? – Chris Collett Apr 19 '21 at 17:42
  • @Engineer_Chris: it is highly unlikely that `displaylines` isn't working properly or has changed its behavior. It's been a part of tkinter for more than a decade, and the tk team is very serious about testing and backward-compatibility. – Bryan Oakley Apr 19 '21 at 17:49
  • @BryanOakley Ah, I see now. `displaylines` behavior changes before and after the window is displayed. `update` needs to be called (or another equivalent method) to format the line, then `displaylines` knows how many lines to return. From your answer https://stackoverflow.com/a/51124558/10792235 – Chris Collett Apr 19 '21 at 17:57