Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to speed up scrolling responsiveness when displaying lots of text

I am trying to create a Python script to highlight specific patterns in a .txt file. To do this, I have altered a script which used Tkinter to highlight a given set of data. However, the files I tend to get it to process are around 10000 lines, which results in slow scrolling as I think it renders everything - whether it is on the screen or not (correct me if I'm wrong). Is it possible to alter my code such that it renders the output in a more efficient way? I have tried searching for a means to do this, but have not found anything myself.

My code is as follows:

from Tkinter import *

class FullScreenApp(object):
    def __init__(self, master, **kwargs):
        self.master=master
        pad=3
        self._geom='200x200+0+0'
        master.geometry("{0}x{1}+0+0".format(
            master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))
        master.bind('<Escape>',self.toggle_geom)            
    def toggle_geom(self,event):
        geom=self.master.winfo_geometry()
        print(geom,self._geom)
        self.master.geometry(self._geom)
        self._geom=geom

root = Tk()
app = FullScreenApp(root)
t = Text(root)
t.pack()

#Import file
with open('data.txt') as f:
    for line in f:
        t.insert(END, line)

#Search terms - Leave blank if not required       
search_term0 = '0xCAFE'
search_term1 = '0x0011'
search_term2 = '0x961E'
search_term3 = '0x0000'
search_term4 = ''

#Assigns highlighted colours for terms not blank
t.tag_config(search_term0, background='red')
if search_term1 != '':
    t.tag_config(search_term1, background='red')
if search_term2 != '':    
    t.tag_config(search_term2, background='red')
if search_term3 != '':
    t.tag_config(search_term3, background='red')
if search_term4 != '':
    t.tag_config(search_term4, background='red')

#Define search
#Requires text widget, the keyword, and a tag
def search(text_widget, keyword, tag):
    pos = '1.0'
    while True:
        idx = text_widget.search(keyword, pos, END)
        if not idx:
            break
        pos = '{}+{}c'.format(idx, len(keyword))
        text_widget.tag_add(tag, idx, pos)

#Search for terms that are not blank
search(t, search_term0, search_term0)
if search_term1 != '':
    search(t, search_term1, search_term1)
if search_term2 != '':
    search(t, search_term2, search_term2)
if search_term3 != '':
    search(t, search_term3, search_term3)
if search_term4 != '':
    search(t, search_term4, search_term3)

root.mainloop()

An example of the data in a file is given in the following link: here

Many thanks for your time, it is really appreciated.

like image 220
T. Anserson Avatar asked Aug 03 '16 20:08

T. Anserson


1 Answers

Assuming MCVE is the following:

import tkinter as tk


def create_text(text_len):
    _text = list()
    for _ in range(text_len):
        _text.append("{}\n".format(_))
    _text = "".join(_text)
    return _text


if __name__ == '__main__':
    root = tk.Tk()
    txt = tk.Text(root)

    txt.text = create_text(10000)
    txt.insert('end', txt.text)

    txt.pack()
    root.mainloop()

Analysis

Based on this I don't think it is a rendering issue. I think it's an issue with having a fixed rate of registering <KeyPress> events. Meaning that the number of events registered per second is fixed, even though the hardware may be capable of registering at a faster rate. A similar regulation should be true also for the mouse-scroll event.


Solutions for rendering

Perhaps slicing text for a buffer proportion of txt['height'] would help. But isn't that how Tk supposed to be rendering anyway?


Solutions for rendering unrelated issue

If a step would be defined as the cursor's movement to the previous or the next line, for every registered event of Up or Down; then scrolling_speed = step * event_register_frequency.


By increasing the step size

An easy workaround would be to simply increase the step size, as in increasing the number of lines to jump, for each registration of the key bind.

But there's already such default behavior, assuming the page length > 1 line, Page Up or Page Down has a step size of a page. Which makes the scrolling speed increase, even though the event registration rate remains the same.

Alternatively, a new event handler with a greater step size may be defined to call multiple cursor movements for each registration of Up and Down, such as:

import tkinter as tk


def create_text(text_len):
    _text = list()
    for _ in range(text_len):
        _text.append("{}\n".format(_))
    _text = "".join(_text)
    return _text


def step(event):
    if txt._step_size != 1:
        _no_of_lines_to_jump = txt._step_size
        if event.keysym == 'Up':
            _no_of_lines_to_jump *= -1

        _position = root.tk.call('tk::TextUpDownLine', txt, _no_of_lines_to_jump)
        root.tk.call('tk::TextSetCursor', txt, _position)
        return "break"

if __name__ == '__main__':
    root = tk.Tk()
    txt = tk.Text(root)

    txt.text = create_text(10000)
    txt.insert('end', txt.text)

    txt._step_size = 12
    txt.bind("<Up>", step)
    txt.bind("<Down>", step)

    txt.pack()
    root.mainloop()

By mimicking keypress event registry rate increase:

As mentioned in here actually modifying keypress registry rate is out of scope of Tk. Instead, it can be mimicked:

import tkinter as tk


def create_text(text_len):
    _text = list()
    for _ in range(text_len):
        _text.append("{}\n".format(_))
    _text = "".join(_text)
    return _text


def step_up(*event):
    _position = root.tk.call('tk::TextUpDownLine', txt, -1)
    root.tk.call('tk::TextSetCursor', txt, _position)

    if txt._repeat_on:
        root.after(txt._repeat_freq, step_up)
    return "break"


def step_down(*event):
    _position = root.tk.call('tk::TextUpDownLine', txt, 1)
    root.tk.call('tk::TextSetCursor', txt, _position)

    if txt._repeat_on:
        root.after(txt._repeat_freq, step_down)
    return "break"


def stop(*event):
    if txt._repeat_on:
        txt._repeat_on = False
        root.after(txt._repeat_freq + 1, stop)
    else:
        txt._repeat_on = True

if __name__ == '__main__':

    root = tk.Tk()

    txt = tk.Text(root)

    txt.text = create_text(10000)
    txt.insert('end', txt.text)
    txt._repeat_freq = 100
    txt._repeat_on = True

    txt.bind("<KeyPress-Up>", step_up)
    txt.bind("<KeyRelease-Up>", stop)
    txt.bind("<KeyPress-Down>", step_down)
    txt.bind("<KeyRelease-Down>", stop)

    txt.pack()
    root.mainloop()

By both increasing step-size and mimicking registry rate increase

import tkinter as tk


def create_text(text_len):
    _text = list()
    for _ in range(text_len):
        _text.append("{}\n".format(_))
    _text = "".join(_text)
    return _text


def step_up(*event):
    _no_of_lines_to_jump = -txt._step_size
    _position = root.tk.call('tk::TextUpDownLine', txt, _no_of_lines_to_jump)
    root.tk.call('tk::TextSetCursor', txt, _position)

    if txt._repeat_on:
        root.after(txt._repeat_freq, step_up)
    return "break"


def step_down(*event):
    _no_of_lines_to_jump = txt._step_size
    _position = root.tk.call('tk::TextUpDownLine', txt, _no_of_lines_to_jump)
    root.tk.call('tk::TextSetCursor', txt, _position)

    if txt._repeat_on:
        root.after(txt._repeat_freq, step_down)
    return "break"


def stop(*event):
    if txt._repeat_on:
        txt._repeat_on = False
        root.after(txt._repeat_freq + 1, stop)
    else:
        txt._repeat_on = True

if __name__ == '__main__':

    root = tk.Tk()

    txt = tk.Text(root)

    txt.text = create_text(10000)
    txt.insert('end', txt.text)
    txt._step_size = 1
    txt._repeat_freq = 100
    txt._repeat_on = True

    txt.bind("<KeyPress-Up>", step_up)
    txt.bind("<KeyRelease-Up>", stop)
    txt.bind("<KeyPress-Down>", step_down)
    txt.bind("<KeyRelease-Down>", stop)

    txt.pack()
    root.mainloop()
like image 69
Nae Avatar answered Nov 17 '22 08:11

Nae