how to wait for a pause in user input in a gtk.TextBuffer?

I'm trying to write a simple gui-based application in pygtk which provides 'live' previewing of text-based markup. The markup processing, however, can be quite computationally expensive and slow to run, so updating the preview on every keystroke is not really viable. Instead I'd like to have the update run only when user input lapses. Ideally, I'd have it update a specified interval after the last keystroke in a sequence.

I've looked into using threading.Timer, by cancelling and re-starting a timer to invoke the update function each time the "changed" signal is emitted. I've also tried to use gtk.idle_add(), but I can't seem to get it working.

Below is a very simple example - could someone suggest the best strategy to achieve what I'm after, preferably with an example?

import gtk

class example:
    def __init__(self):
        window = gtk.Window()
        window.set_title("example")
        window.resize(600,400)
        box = gtk.HBox(homogeneous = True, spacing = 2)
        buf = gtk.TextBuffer()
        buf.connect("changed", self.buf_on_change)
        textInput = gtk.TextView(buf)
        box.add(textInput)
        self.lbl = gtk.Label()
        box.add(self.lbl)
        window.add(box)
        window.connect("destroy", gtk.main_quit)
        window.show_all()

    def buf_on_change(self, buf):
        txt = buf.get_text(*buf.get_bounds())
        output = self.renderText(txt)
        self.lbl.set_text(output)

    def renderText(self, txt):
        # perform computation-intensive text-manipulation here
        output = txt
        return output

if __name__ == '__main__':
    example()
    gtk.main()

Answers


so I think I found a solution using glib.timeout_add() instead of threading.Timer:

import gtk, glib

class example:
    def __init__(self):
        window = gtk.Window()
        window.set_title("example")
        window.resize(600,400)
        box = gtk.HBox(homogeneous = True, spacing = 2)
        self.buf = gtk.TextBuffer()
        self.buf.connect("changed", self.buf_on_change)
        textInput = gtk.TextView(self.buf)
        box.add(textInput)
        self.lbl = gtk.Label()
        box.add(self.lbl)
        window.add(box)
        window.connect("destroy", gtk.main_quit)
        window.show_all()

        self.timer = glib.timeout_add(1000, self.renderText)

    def buf_on_change(self, buf):
        glib.source_remove(self.timer)
        self.timer = glib.timeout_add(1000, self.renderText)


    def renderText(self):
        txt = self.buf.get_text(*self.buf.get_bounds())
        # perform computation-intensive text-manipulation here
        self.lbl.set_text(txt)
        return False

if __name__ == '__main__':
    example()
    gtk.main()

this seems to work as desired, but since I'm completely new to gtk (and desktop dui programming in general - in case you couldn't tell ;) ) I'd like to leave this question open in the hope that someone more experience might comment on the best way to achieve this kind of effect. I hope that's okay?


So i did not have pygtk, so i mimiced your problem using termios and a home made loop for the text input. But the part about the timer should work in gtk too. Please note that the access to the self.timer is not thread safe if you use an object of class example within more than one thread at the time you must lock self.txt and self.timer.

import termios, fcntl, sys, os, time, threading
fd = sys.stdin.fileno()

#just setting up the terminal input
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)


class example:
  def __init__(self, timeout):
    self.timeout = timeout
    self.timer = threading.Timer(timeout, self.render_text)
    self.txt = ''
    self.timer.start() #at the end of __init__ 

  def buf_on_change(self, buf):
    print "Got buffer:", buf, time.ctime()
    self.txt = self.txt + buf
    self.timer.cancel()   #won't do no harm if timer already elapsed
    self.timer = threading.Timer(self.timeout, self.render_text)
    self.timer.start()

  def render_text(self):
    print "starting intensive computation"
    print "rendering", self.txt
    time.sleep(3)
    print "stopping intensive computation"


# just my poor mans event loop for the text input
obj = example(3)
try:
    while 1:
        time.sleep(0.001) #just to keep processor cool
        try:
            buf = sys.stdin.read(5)
            obj.buf_on_change(buf)
        except IOError: pass
finally:
    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

Need Your Help

How to create an NSInvocation object using a method that takes a pointer to an object as an argument

ios cocoa-touch automatic-ref-counting nserror nsinvocation

I would like to create an NSInvocation object using a method that takes a pointer to an NSError object as an argument. An example of this would be the method -

Does erlang ChicagoBoss webserver support couchdb (couchbase)?

erlang couchbase chicagoboss

I am looking to use erlang ChicagoBoss webserver to develop webapp with Couchbase(Couchdb) as backend. But I don't see adapter for it other than Riak and Mongodb. Any suggestions?