[Tkinter-discuss] Problem with Text widget's "see" method

Jared Cohen Jared.Cohen at noaa.gov
Mon Feb 14 19:02:44 CET 2005


I'm building a text editor that has an Undo/Redo feature. To do this, I 
basically invoke a callback every time the user presses a key; the 
callback copies the entire contents of the Text widget to a history 
list. Later, if the user presses Ctrl-Z, the widget steps back through 
the history list, and if the user presses Ctrl-Y, it steps forward.

Now, I not only want the widget to recall its previous contents when the 
user does an Undo, but I also want it to remember the position of the 
insertion cursor and automatically set the cursor to that position. I 
did this by saving the index (i.e. "1.0") of the cursor each time a new 
history item is added; then, when that item is recalled, I can set the 
cursor back to that index. Of course, the Text widget doesn't actually 
HAVE a method to explicitly set the position of the cursor; so I 
simulated that behavior by using event_generate to pretend the user had 
actually clicked at the desired position.

Now, here's the problem. The "pretend clicking" method will only work if 
it can get the on-screen coordinates of the index, which means the index 
needs to actually be on screen. To ensure that it is, I used the Text 
widget's "see" method, which is supposed to scroll the widget so the 
desired index is visible, IF it isn't already visible.

But the "see" method doesn't work correctly. If the user enters text on 
the first "screen" (i.e. when the scroll bar is at the top) and then 
hits Ctrl-Z, no scrolling occurs -- the index is still on screen. But if 
the added text is on a later "screen", then the widget scrolls so the 
index is in the center of the screen, even if it's already visible! I 
tried explicitly using the "bbox" method, which is supposed to return 
None if the index is off-screen; but that didn't work either!

I think the problem is that when I perform an Undo, I completely erase 
the Text widget and refill it with the previous history item -- this 
seems to mess up the indices somehow. Any thoughts as to how I can fix this?

Here's some sample code. Be warned, it's pretty long. To test it, first 
scroll down a few pages; type some characters into the text widget; 
scroll down a little more, but make sure the new text is still visible; 
and then hit Ctrl-Z. The added text is still visible, so the widget 
SHOULDN'T scroll, but it does anyway.
----------------------------------------------------------------------------------------

#!/usr/bin/env python
import sys, os, Tkinter, types, tkFont, Pmw


def makeResizable(widget):
    for i in range(0, widget.grid_size()[0]):
        widget.columnconfigure(i, weight=1)
    for i in range(0, widget.grid_size()[1]):
        widget.rowconfigure(i, weight=1)

class seeTest:

    def __init__(self, root):

        self.root = root

        self.tableText = Pmw.ScrolledText(self.root,
                                                                            
text_bg='white',
                                                                            
text_wrap='none')
        self.tableText.grid(row=0, rowspan=20, column=0, columnspan=20, 
sticky='nsew')

        fileName = "/home/textfile.txt" #this can be any text file
        file = open(fileName, "r")
        lines = file.readlines()
        file.close()
        for l in lines:
            self.tableText.component('text').insert("end", l)
        self.tableText.component('text').see("1.0")

        self.root.update_idletasks()
        self.history = [(self.tableText.getvalue(), "1.0"), ]
        self.curHist = 0

        self.tableText.component('text').bind("<Any-KeyPress>", 
self.AddHistory)
        self.tableText.component('text').bind("<Control-Any-KeyPress>", 
self.AddHistory)
        self.tableText.component('text').bind("<Any-KeyRelease>", 
self.AddHistory)
        
self.tableText.component('text').bind("<Control-Any-KeyRelease>", 
self.AddHistory)
        self.tableText.component('text').bind("<KeyPress-Control_L>", 
self.Dummy)
        self.tableText.component('text').bind("<KeyPress-Control_R>", 
self.Dummy)
        self.tableText.component('text').bind("<KeyRelease-Control_L>", 
self.Dummy)
        self.tableText.component('text').bind("<KeyRelease-Control_R>", 
self.Dummy)
        self.tableText.component('text').bind("<Control-KeyPress-z>", 
self.Undo)
        self.tableText.component('text').bind("<Control-KeyPress-Z>", 
self.Undo)
        self.tableText.component('text').bind("<Control-KeyRelease-z>", 
self.Dummy)
        self.tableText.component('text').bind("<Control-KeyRelease-Z>", 
self.Dummy)

        self.root.update()
        makeResizable(self.root)

    def AddHistory(self, *args):
        self.tableText.update_idletasks()
        text = self.tableText.getvalue()
        if text != (self.history[self.curHist])[0]:
            while self.curHist < len(self.history)-1:
                self.history.pop()
            self.root.update_idletasks()
            index = 
str(self.tableText.component('text').index(Tkinter.INSERT))
            self.history.append( (text, index) )
            self.curHist += 1

    def Undo(self, *args):
        if self.curHist == 0:
            self.root.bell()
        else:
            self.curHist -= 1
            text = (self.history[self.curHist])[0]
            if text[-1] == '\n':
                text = text[:-1]
            if self.curHist == 0:
                index = (self.history[1])[1]
            else:
                index = (self.history[self.curHist])[1]
            self.tableText.component('text').delete("1.0", "end")
            self.tableText.component('text').insert("end", text)

            self.tableText.component('text').update_idletasks()
            self.tableText.component('text').see(index)
        return "break"

    def Dummy(self, *args):
        return "break"

def main():

    root = Tkinter.Tk()
    root.geometry("1000x600+0+0")
    Pmw.initialise(root)
    test = seeTest(root)

    root.mainloop()

if __name__=='__main__': main()


More information about the Tkinter-discuss mailing list