Index: ColorDelegator.py =================================================================== --- ColorDelegator.py (revision 58404) +++ ColorDelegator.py (working copy) @@ -248,16 +248,17 @@ self.tag_remove(tag, "1.0", "end") def main(): - from Percolator import Percolator + from Percolator import TkTextPercolator root = Tk() root.wm_protocol("WM_DELETE_WINDOW", root.quit) text = Text(background="white") text.pack(expand=1, fill="both") text.focus_set() - p = Percolator(text) + p = TkTextPercolator(text) d = ColorDelegator() p.insertfilter(d) root.mainloop() + root.destroy() if __name__ == "__main__": main() Index: EditorWindow.py =================================================================== --- EditorWindow.py (revision 58404) +++ EditorWindow.py (working copy) @@ -40,7 +40,7 @@ return file, filename, descr class EditorWindow(object): - from Percolator import Percolator + from Percolator import TkTextPercolator from ColorDelegator import ColorDelegator from UndoDelegator import UndoDelegator from IOBinding import IOBinding, filesystemencoding, encoding @@ -223,7 +223,7 @@ # Making the initial values larger slows things down more often. self.num_context_lines = 50, 500, 5000000 - self.per = per = self.Percolator(text) + self.per = per = self.TkTextPercolator(text) if self.ispythonsource(filename): self.color = color = self.ColorDelegator() per.insertfilter(color) @@ -589,17 +589,14 @@ if not self.color: return self.color.removecolors() - self.per.removefilter(self.undo) self.per.removefilter(self.color) self.color = None - self.per.insertfilter(self.undo) def ResetColorizer(self): "Update the colour theme if it is changed" # Called from configDialog.py - if self.color: - self.color = self.ColorDelegator() - self.per.insertfilter(self.color) + self.rmcolorizer() + self.addcolorizer() theme = idleConf.GetOption('main','Theme','name') self.text.config(idleConf.GetHighlight(theme, "normal")) @@ -838,7 +835,7 @@ self.color = None self.text = None self.tkinter_vars = None - self.per.close() + self.per.percolator_close() self.per = None self.top.destroy() if self.close_hook: Index: Percolator.py =================================================================== --- Percolator.py (revision 58404) +++ Percolator.py (working copy) @@ -1,57 +1,108 @@ from WidgetRedirector import WidgetRedirector from Delegator import Delegator -class Percolator: - def __init__(self, text): - # XXX would be nice to inherit from Delegator - self.text = text - self.redir = WidgetRedirector(text) - self.top = self.bottom = Delegator(text) - self.bottom.insert = self.redir.register("insert", self.insert) - self.bottom.delete = self.redir.register("delete", self.delete) - self.filters = [] +class Percolator(Delegator): + """A linked list of Delegator objects, each delegating to the next. - def close(self): + A Percolator is a Delegator, and therefore inherits from Delegator. + + Use 'insertfilter' to add a Delegator to the beginning of the list, + and 'removefilter' to remove a Delegator from (anywhere in) the list. + + 'setdelegate' and 'getdelegate' refer to the original delegate, to which + the last delegator in the list delegates. + + """ + + def __init__(self, delegate): + Delegator.__init__(self) + self.top = self.bottom = Delegator(delegate) + + # Always actually delegate to self.top + top = property(lambda self: Delegator.getdelegate(self), + lambda self, delegate: Delegator.setdelegate(self, delegate)) + + # setdelegate and getdelegate refer to the original delegate + def setdelegate(self, delegate): + self.bottom.setdelegate(delegate) + self.resetcache() + f = self.top + while f is not self.bottom: + f.resetcache() + f = f.getdelegate() + + def getdelegate(self): + return self.bottom.getdelegate() + + + def percolator_close(self): while self.top is not self.bottom: self.removefilter(self.top) - self.top = None - self.bottom.setdelegate(None); self.bottom = None - self.redir.close(); self.redir = None - self.text = None + self.setdelegate(None) +# self.top = None - def insert(self, index, chars, tags=None): - # Could go away if inheriting from Delegator - self.top.insert(index, chars, tags) - - def delete(self, index1, index2=None): - # Could go away if inheriting from Delegator - self.top.delete(index1, index2) - def insertfilter(self, filter): # Perhaps rename to pushfilter()? assert isinstance(filter, Delegator) - assert filter.delegate is None + assert filter.getdelegate() is None filter.setdelegate(self.top) self.top = filter def removefilter(self, filter): # XXX Perhaps should only support popfilter()? assert isinstance(filter, Delegator) - assert filter.delegate is not None + assert filter.getdelegate() is not None f = self.top if f is filter: - self.top = filter.delegate - filter.setdelegate(None) + self.top = filter.getdelegate() else: while f.delegate is not filter: assert f is not self.bottom f.resetcache() - f = f.delegate - f.setdelegate(filter.delegate) - filter.setdelegate(None) + f = f.getdelegate() + f.setdelegate(filter.getdelegate()) + filter.setdelegate(None) +class TkTextPercolator(Percolator): + """A specialized Percolator used to wrap a Tk Text widget. + + The Text widget's insert and delete methods are redirected to the top of + the Percolator. The bottom-most Delegator has the original insert and + delete methods. + + This uses the WidgetRedirector class which allows redirecting Tcl/Tk calls + to Python functions/methods. + + """ + def __init__(self, text): + Percolator.__init__(self, text) + self.redir = WidgetRedirector(text) + + # Register functions which dynamically retrieve the methods (see below) + self.bottom.insert = self.redir.register("insert", self.insert) + self.bottom.delete = self.redir.register("delete", self.delete) + + def close(self): + self.redir.close(); self.redir = None + Percolator.close(self) + + # The following methods ensure proper dynamic attribute resolution. Without + # these, adding filters after contsruction would have no effect. + # + # For instance, for the 'insert' method, this ensures that 'self.insert' is + # properly resolved. Without this, the widget's 'insert' method would be + # redirected to whatever self.insert is delegated to during construction, + # since self.insert is evaluated when __init__ is run. (likewise for + # 'delete') + def insert(self, index, chars, tags=None): + self.top.insert(index, chars, tags) + + def delete(self, index1, index2=None): + self.top.delete(index1, index2) + + def main(): class Tracer(Delegator): def __init__(self, name): @@ -63,12 +114,13 @@ def delete(self, *args): print self.name, ": delete", args self.delegate.delete(*args) + from Tkinter import Tk, Text root = Tk() root.wm_protocol("WM_DELETE_WINDOW", root.quit) text = Text() text.pack() text.focus_set() - p = Percolator(text) + p = TkTextPercolator(text) t1 = Tracer("t1") t2 = Tracer("t2") p.insertfilter(t1) @@ -79,7 +131,7 @@ p.insertfilter(t2) p.removefilter(t1) root.mainloop() + root.destroy() if __name__ == "__main__": - from Tkinter import * main() Index: UndoDelegator.py =================================================================== --- UndoDelegator.py (revision 58404) +++ UndoDelegator.py (working copy) @@ -337,16 +337,17 @@ return self.depth def main(): - from Percolator import Percolator + from Percolator import TkTextPercolator root = Tk() root.wm_protocol("WM_DELETE_WINDOW", root.quit) text = Text() text.pack() text.focus_set() - p = Percolator(text) + p = TkTextPercolator(text) d = UndoDelegator() p.insertfilter(d) root.mainloop() + root.destroy() if __name__ == "__main__": main()