PyXR

c:\python24\lib \ idlelib \ UndoDelegator.py



0001 import sys
0002 import string
0003 from Tkinter import *
0004 from Delegator import Delegator
0005 
0006 #$ event <<redo>>
0007 #$ win <Control-y>
0008 #$ unix <Alt-z>
0009 
0010 #$ event <<undo>>
0011 #$ win <Control-z>
0012 #$ unix <Control-z>
0013 
0014 #$ event <<dump-undo-state>>
0015 #$ win <Control-backslash>
0016 #$ unix <Control-backslash>
0017 
0018 
0019 class UndoDelegator(Delegator):
0020 
0021     max_undo = 1000
0022 
0023     def __init__(self):
0024         Delegator.__init__(self)
0025         self.reset_undo()
0026 
0027     def setdelegate(self, delegate):
0028         if self.delegate is not None:
0029             self.unbind("<<undo>>")
0030             self.unbind("<<redo>>")
0031             self.unbind("<<dump-undo-state>>")
0032         Delegator.setdelegate(self, delegate)
0033         if delegate is not None:
0034             self.bind("<<undo>>", self.undo_event)
0035             self.bind("<<redo>>", self.redo_event)
0036             self.bind("<<dump-undo-state>>", self.dump_event)
0037 
0038     def dump_event(self, event):
0039         from pprint import pprint
0040         pprint(self.undolist[:self.pointer])
0041         print "pointer:", self.pointer,
0042         print "saved:", self.saved,
0043         print "can_merge:", self.can_merge,
0044         print "get_saved():", self.get_saved()
0045         pprint(self.undolist[self.pointer:])
0046         return "break"
0047 
0048     def reset_undo(self):
0049         self.was_saved = -1
0050         self.pointer = 0
0051         self.undolist = []
0052         self.undoblock = 0  # or a CommandSequence instance
0053         self.set_saved(1)
0054 
0055     def set_saved(self, flag):
0056         if flag:
0057             self.saved = self.pointer
0058         else:
0059             self.saved = -1
0060         self.can_merge = False
0061         self.check_saved()
0062 
0063     def get_saved(self):
0064         return self.saved == self.pointer
0065 
0066     saved_change_hook = None
0067 
0068     def set_saved_change_hook(self, hook):
0069         self.saved_change_hook = hook
0070 
0071     was_saved = -1
0072 
0073     def check_saved(self):
0074         is_saved = self.get_saved()
0075         if is_saved != self.was_saved:
0076             self.was_saved = is_saved
0077             if self.saved_change_hook:
0078                 self.saved_change_hook()
0079 
0080     def insert(self, index, chars, tags=None):
0081         self.addcmd(InsertCommand(index, chars, tags))
0082 
0083     def delete(self, index1, index2=None):
0084         self.addcmd(DeleteCommand(index1, index2))
0085 
0086     # Clients should call undo_block_start() and undo_block_stop()
0087     # around a sequence of editing cmds to be treated as a unit by
0088     # undo & redo.  Nested matching calls are OK, and the inner calls
0089     # then act like nops.  OK too if no editing cmds, or only one
0090     # editing cmd, is issued in between:  if no cmds, the whole
0091     # sequence has no effect; and if only one cmd, that cmd is entered
0092     # directly into the undo list, as if undo_block_xxx hadn't been
0093     # called.  The intent of all that is to make this scheme easy
0094     # to use:  all the client has to worry about is making sure each
0095     # _start() call is matched by a _stop() call.
0096 
0097     def undo_block_start(self):
0098         if self.undoblock == 0:
0099             self.undoblock = CommandSequence()
0100         self.undoblock.bump_depth()
0101 
0102     def undo_block_stop(self):
0103         if self.undoblock.bump_depth(-1) == 0:
0104             cmd = self.undoblock
0105             self.undoblock = 0
0106             if len(cmd) > 0:
0107                 if len(cmd) == 1:
0108                     # no need to wrap a single cmd
0109                     cmd = cmd.getcmd(0)
0110                 # this blk of cmds, or single cmd, has already
0111                 # been done, so don't execute it again
0112                 self.addcmd(cmd, 0)
0113 
0114     def addcmd(self, cmd, execute=True):
0115         if execute:
0116             cmd.do(self.delegate)
0117         if self.undoblock != 0:
0118             self.undoblock.append(cmd)
0119             return
0120         if self.can_merge and self.pointer > 0:
0121             lastcmd = self.undolist[self.pointer-1]
0122             if lastcmd.merge(cmd):
0123                 return
0124         self.undolist[self.pointer:] = [cmd]
0125         if self.saved > self.pointer:
0126             self.saved = -1
0127         self.pointer = self.pointer + 1
0128         if len(self.undolist) > self.max_undo:
0129             ##print "truncating undo list"
0130             del self.undolist[0]
0131             self.pointer = self.pointer - 1
0132             if self.saved >= 0:
0133                 self.saved = self.saved - 1
0134         self.can_merge = True
0135         self.check_saved()
0136 
0137     def undo_event(self, event):
0138         if self.pointer == 0:
0139             self.bell()
0140             return "break"
0141         cmd = self.undolist[self.pointer - 1]
0142         cmd.undo(self.delegate)
0143         self.pointer = self.pointer - 1
0144         self.can_merge = False
0145         self.check_saved()
0146         return "break"
0147 
0148     def redo_event(self, event):
0149         if self.pointer >= len(self.undolist):
0150             self.bell()
0151             return "break"
0152         cmd = self.undolist[self.pointer]
0153         cmd.redo(self.delegate)
0154         self.pointer = self.pointer + 1
0155         self.can_merge = False
0156         self.check_saved()
0157         return "break"
0158 
0159 
0160 class Command:
0161 
0162     # Base class for Undoable commands
0163 
0164     tags = None
0165 
0166     def __init__(self, index1, index2, chars, tags=None):
0167         self.marks_before = {}
0168         self.marks_after = {}
0169         self.index1 = index1
0170         self.index2 = index2
0171         self.chars = chars
0172         if tags:
0173             self.tags = tags
0174 
0175     def __repr__(self):
0176         s = self.__class__.__name__
0177         t = (self.index1, self.index2, self.chars, self.tags)
0178         if self.tags is None:
0179             t = t[:-1]
0180         return s + repr(t)
0181 
0182     def do(self, text):
0183         pass
0184 
0185     def redo(self, text):
0186         pass
0187 
0188     def undo(self, text):
0189         pass
0190 
0191     def merge(self, cmd):
0192         return 0
0193 
0194     def save_marks(self, text):
0195         marks = {}
0196         for name in text.mark_names():
0197             if name != "insert" and name != "current":
0198                 marks[name] = text.index(name)
0199         return marks
0200 
0201     def set_marks(self, text, marks):
0202         for name, index in marks.items():
0203             text.mark_set(name, index)
0204 
0205 
0206 class InsertCommand(Command):
0207 
0208     # Undoable insert command
0209 
0210     def __init__(self, index1, chars, tags=None):
0211         Command.__init__(self, index1, None, chars, tags)
0212 
0213     def do(self, text):
0214         self.marks_before = self.save_marks(text)
0215         self.index1 = text.index(self.index1)
0216         if text.compare(self.index1, ">", "end-1c"):
0217             # Insert before the final newline
0218             self.index1 = text.index("end-1c")
0219         text.insert(self.index1, self.chars, self.tags)
0220         self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
0221         self.marks_after = self.save_marks(text)
0222         ##sys.__stderr__.write("do: %s\n" % self)
0223 
0224     def redo(self, text):
0225         text.mark_set('insert', self.index1)
0226         text.insert(self.index1, self.chars, self.tags)
0227         self.set_marks(text, self.marks_after)
0228         text.see('insert')
0229         ##sys.__stderr__.write("redo: %s\n" % self)
0230 
0231     def undo(self, text):
0232         text.mark_set('insert', self.index1)
0233         text.delete(self.index1, self.index2)
0234         self.set_marks(text, self.marks_before)
0235         text.see('insert')
0236         ##sys.__stderr__.write("undo: %s\n" % self)
0237 
0238     def merge(self, cmd):
0239         if self.__class__ is not cmd.__class__:
0240             return False
0241         if self.index2 != cmd.index1:
0242             return False
0243         if self.tags != cmd.tags:
0244             return False
0245         if len(cmd.chars) != 1:
0246             return False
0247         if self.chars and \
0248            self.classify(self.chars[-1]) != self.classify(cmd.chars):
0249             return False
0250         self.index2 = cmd.index2
0251         self.chars = self.chars + cmd.chars
0252         return True
0253 
0254     alphanumeric = string.ascii_letters + string.digits + "_"
0255 
0256     def classify(self, c):
0257         if c in self.alphanumeric:
0258             return "alphanumeric"
0259         if c == "\n":
0260             return "newline"
0261         return "punctuation"
0262 
0263 
0264 class DeleteCommand(Command):
0265 
0266     # Undoable delete command
0267 
0268     def __init__(self, index1, index2=None):
0269         Command.__init__(self, index1, index2, None, None)
0270 
0271     def do(self, text):
0272         self.marks_before = self.save_marks(text)
0273         self.index1 = text.index(self.index1)
0274         if self.index2:
0275             self.index2 = text.index(self.index2)
0276         else:
0277             self.index2 = text.index(self.index1 + " +1c")
0278         if text.compare(self.index2, ">", "end-1c"):
0279             # Don't delete the final newline
0280             self.index2 = text.index("end-1c")
0281         self.chars = text.get(self.index1, self.index2)
0282         text.delete(self.index1, self.index2)
0283         self.marks_after = self.save_marks(text)
0284         ##sys.__stderr__.write("do: %s\n" % self)
0285 
0286     def redo(self, text):
0287         text.mark_set('insert', self.index1)
0288         text.delete(self.index1, self.index2)
0289         self.set_marks(text, self.marks_after)
0290         text.see('insert')
0291         ##sys.__stderr__.write("redo: %s\n" % self)
0292 
0293     def undo(self, text):
0294         text.mark_set('insert', self.index1)
0295         text.insert(self.index1, self.chars)
0296         self.set_marks(text, self.marks_before)
0297         text.see('insert')
0298         ##sys.__stderr__.write("undo: %s\n" % self)
0299 
0300 class CommandSequence(Command):
0301 
0302     # Wrapper for a sequence of undoable cmds to be undone/redone
0303     # as a unit
0304 
0305     def __init__(self):
0306         self.cmds = []
0307         self.depth = 0
0308 
0309     def __repr__(self):
0310         s = self.__class__.__name__
0311         strs = []
0312         for cmd in self.cmds:
0313             strs.append("    %r" % (cmd,))
0314         return s + "(\n" + ",\n".join(strs) + "\n)"
0315 
0316     def __len__(self):
0317         return len(self.cmds)
0318 
0319     def append(self, cmd):
0320         self.cmds.append(cmd)
0321 
0322     def getcmd(self, i):
0323         return self.cmds[i]
0324 
0325     def redo(self, text):
0326         for cmd in self.cmds:
0327             cmd.redo(text)
0328 
0329     def undo(self, text):
0330         cmds = self.cmds[:]
0331         cmds.reverse()
0332         for cmd in cmds:
0333             cmd.undo(text)
0334 
0335     def bump_depth(self, incr=1):
0336         self.depth = self.depth + incr
0337         return self.depth
0338 
0339 def main():
0340     from Percolator import Percolator
0341     root = Tk()
0342     root.wm_protocol("WM_DELETE_WINDOW", root.quit)
0343     text = Text()
0344     text.pack()
0345     text.focus_set()
0346     p = Percolator(text)
0347     d = UndoDelegator()
0348     p.insertfilter(d)
0349     root.mainloop()
0350 
0351 if __name__ == "__main__":
0352     main()
0353 

Generated by PyXR 0.9.4
SourceForge.net Logo