python 3 tkinter continue text widget tag with natural input after tag range terminator

Viewed 181

Background:

I'm trying to get an MS Word like behavior. I want text inserted immediately adjacent to the end of a tag range to be automatically included in that tag's range.

Text that is inserted within a tag range acts as I would expect within MS word. Colored text added is displays as the correct color, font, styles immediately.

The same is not true when updating added text with tag_add(). You can see the update as a visual 'hiccup'. Text starts as the default text and pops into the tag styling. Additionally, when typing quickly the tag_add() 'loses' the tag range. That is not acceptable behavior for my application.

I have been looking for a few days and can't find anything even related to how tkinter decided how to deal with growing tags naturally i.e. without Text.tag_add() or Text.insert().


Question:

How do I set tkinter to prefer the tag in the left adjacent index when deciding what range to add the new keyboard input text.


Demo:

I have a demo video on my blog along with the coed of my current solution. I do not consider the current state usable/functional. So I need something that updates the styling seamlessly.

Tri it! Blog Post


Required Technology:

python 3+ and tkinter on windows


Desired Results:

The base range, insert is just after the 'l' in 'terminal'

Base tag range

The next character insert should continue the left styling as if it were within those tag ranges. Without any visual pop from default text to the new styling. And should be unconcerned with the speed of user typing.

Updated tag range


Thank you!

1 Answers

The simplest way to avoid the artifacting I show in the demo. Is to have the tag_add() in a method bound to the root windows "key" event. I am honestly not sure why this is. But the application behaves as expected when bound to the root instead of the Text widget.

For context here is a trimmed down sample of the code from my blog post. I smashed all the check functions down to a few lines. But the important part is the "__ init __" when I bind self.master.bind('', foobar)

Check out video demo on blog for results.

https://valtyrtriit.blogspot.com/2022/02/python-3-win10-tkinter-word-processor_22.html

Do note that "Tab" will not show up on events now because it is a reserved event to navigate window elements. You can break the default binding and rebind it to fix this.

import tkinter as tk
import re

class Document(tk.Text):
    def __init__(self, master, *args, **kewargs):
        super().__init__(*args, **kewargs)
        self.master = master
        self.master.bind("<KeyPress>", self.any_key_down)
        self.tag_add('<b>', "1.0", "1.2")
        self.tag_config('<b>', foreground='red')

    def any_key_down(self, event=None):
        print(event)
        char = re.findall(r"[a-zA-Z0-9\S ]", event.char)
        if 0 < len(char) and event.keysym not in ["BackSpace", "Escape"] or '\t' == event.char:
            insert = event.widget.index('insert-2c')
            tags = event.widget.tag_names(insert)
            for tag in tags:
                event.widget.tag_add(tag, 'insert-1c', 'insert')

root = tk.Tk()
text = Document(root, height=4)
text.pack()

text.insert("1.0", "This is helvetica font", "<b>")
text.insert("1.0", "This is terminal font", "font_term")
text.tag_config('<b>', font='Helvetica 12')
text.tag_config('font_term', font='Terminal 12')
text.insert("3.0", "This is terminal font\n", "font_term")

root.mainloop()
Related