jevan
jevan

Reputation: 71

Python - How To Get Cursor Position in Tkinter Text Widget

I want to get the cursor position (line and column) of the insertion point of a Tkinter.Text, but for the specific situation below.

PROBLEM: My text editor project requires a custom undo/redo for Tkinter.Text. I put in the same string for both Test One and Test Two below, but undo does not act consistently due to a inconsistent column variable in KeyRelease event handler given by Tkinter. The problem seems to be that I type too fast for second test which produces a bad column value. Can you help me find the problem?

TWO TEST PROCESS TO REPRODUCE THE ERROR:

TEST ONE

TEST TWO

QUESTION: Is this a bug in Tkinter, or am I not understanding something specific within Tkinter that would produce consistent columns for my undo/redo records?

from Tkinter import *

class TextView(Text):

    def __init__(self, root):
        Text.__init__(self, root)

        self.history = History(self)
        self.bind("<KeyRelease>", self.keyRelease)

        # used to capture a char at a time in keyRelease.  If space char is pressed it creates a Word object and adds it to undo/redo history.
        self.word = ""

    def keyRelease(self, event):
        if event.keysym == "space":
            self.word += " "
            self.makeWordRecord()
        else:
            self.word += event.char

    def makeWordRecord(self, ):
        if len(self.word):
            index = self.index(INSERT)
            wordEvent = Word(index, self.word)
            self.history.addEvent(wordEvent)
            self.word = ""

    def undo(self, event):
        self.makeWordRecord()
        self.history.undo()

    def redo(self, event):
        self.history.redo()

class History(object):
    def __init__(self, text):
        self.text = text
        self.events = []
        self.index = -1

        # create blank document record, line one, column one, no text
        self.addEvent(Word("1.0", ""))

    def addEvent(self, event):
        if self.index +1 < len(self.events):
            self.events = self.events[:self.index +1]
        self.events.append(event)
        self.index +=1

    def undo(self):
        if self.index > 0:
            self.events[self.index].undo(self.text)
            self.index -=1

    def redo(self):
        if self.index +1  < len(self.events):
            self.index +=1
            self.events[self.index].redo(self.text)

class Word(object):
    def __init__(self, index, word):
        self.index = index
        self.word = word

    def undo(self, text):
        line = self.index.split(".")[0]
        column = int(self.index.split(".")[-1])
        startIndex = line + "." + str(column - len(self.word))
        endIndex = line + "." + str(int(column))
        text.delete(startIndex, endIndex)

    def redo(self, text):
        line = self.index.split(".")[0]
        column = int(self.index.split(".")[-1])
        startIndex = line + "." + str(column - len(self.word))
        text.insert(startIndex, self.word)

if __name__ == "__main__":
    root = Tk()
    root.geometry("400x200+0+0")

    textView = TextView(root)
    textView.pack()
    root.bind("<F1>", textView.undo)
    root.bind("<F2>", textView.redo)

    root.mainloop()

Upvotes: 1

Views: 4403

Answers (1)

jevan
jevan

Reputation: 71

I finally figured out what was going on and has nothing to do with Tkinter, but all Toolkits. I can now honestly say that I can add something to Best Programming Practices and :

Do Not Process Key Events by Binding to a Key Release Method, Instead Process Keys with a Key Press Method

Why?

It's not really a programming issue, it's a hardware issue. When a key is pressed, the physical key goes down. There is a spring that pushes the key back up. If that spring or anything about your keyboard causes a key to be even 200ths of second slower, typing even 60 words a minute may cause keys that were typed in one order to come back in another order. This may happen because a spring may be slightly thicker, stiffer, over used, or even sticky mocha cause more resistance.

Capitalization can be affected as well. Pressing the shift key and another key to get an upper case must be simultaneously pressed down. But if you process keys on key release, it is possible the shift key springs up faster than the character key you are capitalizing, which will result in a lower case. This also allows characters to be inverted. If you check on positions of a character when it's typed, you can get the wrong position due to this as well.

Stick with Key Press.

Upvotes: 5

Related Questions