home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / UndoDelegator.py < prev    next >
Text File  |  2003-12-30  |  10KB  |  353 lines

  1. import sys
  2. import string
  3. from Tkinter import *
  4. from Delegator import Delegator
  5.  
  6. #$ event <<redo>>
  7. #$ win <Control-y>
  8. #$ unix <Alt-z>
  9.  
  10. #$ event <<undo>>
  11. #$ win <Control-z>
  12. #$ unix <Control-z>
  13.  
  14. #$ event <<dump-undo-state>>
  15. #$ win <Control-backslash>
  16. #$ unix <Control-backslash>
  17.  
  18.  
  19. class UndoDelegator(Delegator):
  20.  
  21.     max_undo = 1000
  22.  
  23.     def __init__(self):
  24.         Delegator.__init__(self)
  25.         self.reset_undo()
  26.  
  27.     def setdelegate(self, delegate):
  28.         if self.delegate is not None:
  29.             self.unbind("<<undo>>")
  30.             self.unbind("<<redo>>")
  31.             self.unbind("<<dump-undo-state>>")
  32.         Delegator.setdelegate(self, delegate)
  33.         if delegate is not None:
  34.             self.bind("<<undo>>", self.undo_event)
  35.             self.bind("<<redo>>", self.redo_event)
  36.             self.bind("<<dump-undo-state>>", self.dump_event)
  37.  
  38.     def dump_event(self, event):
  39.         from pprint import pprint
  40.         pprint(self.undolist[:self.pointer])
  41.         print "pointer:", self.pointer,
  42.         print "saved:", self.saved,
  43.         print "can_merge:", self.can_merge,
  44.         print "get_saved():", self.get_saved()
  45.         pprint(self.undolist[self.pointer:])
  46.         return "break"
  47.  
  48.     def reset_undo(self):
  49.         self.was_saved = -1
  50.         self.pointer = 0
  51.         self.undolist = []
  52.         self.undoblock = 0  # or a CommandSequence instance
  53.         self.set_saved(1)
  54.  
  55.     def set_saved(self, flag):
  56.         if flag:
  57.             self.saved = self.pointer
  58.         else:
  59.             self.saved = -1
  60.         self.can_merge = False
  61.         self.check_saved()
  62.  
  63.     def get_saved(self):
  64.         return self.saved == self.pointer
  65.  
  66.     saved_change_hook = None
  67.  
  68.     def set_saved_change_hook(self, hook):
  69.         self.saved_change_hook = hook
  70.  
  71.     was_saved = -1
  72.  
  73.     def check_saved(self):
  74.         is_saved = self.get_saved()
  75.         if is_saved != self.was_saved:
  76.             self.was_saved = is_saved
  77.             if self.saved_change_hook:
  78.                 self.saved_change_hook()
  79.  
  80.     def insert(self, index, chars, tags=None):
  81.         self.addcmd(InsertCommand(index, chars, tags))
  82.  
  83.     def delete(self, index1, index2=None):
  84.         self.addcmd(DeleteCommand(index1, index2))
  85.  
  86.     # Clients should call undo_block_start() and undo_block_stop()
  87.     # around a sequence of editing cmds to be treated as a unit by
  88.     # undo & redo.  Nested matching calls are OK, and the inner calls
  89.     # then act like nops.  OK too if no editing cmds, or only one
  90.     # editing cmd, is issued in between:  if no cmds, the whole
  91.     # sequence has no effect; and if only one cmd, that cmd is entered
  92.     # directly into the undo list, as if undo_block_xxx hadn't been
  93.     # called.  The intent of all that is to make this scheme easy
  94.     # to use:  all the client has to worry about is making sure each
  95.     # _start() call is matched by a _stop() call.
  96.  
  97.     def undo_block_start(self):
  98.         if self.undoblock == 0:
  99.             self.undoblock = CommandSequence()
  100.         self.undoblock.bump_depth()
  101.  
  102.     def undo_block_stop(self):
  103.         if self.undoblock.bump_depth(-1) == 0:
  104.             cmd = self.undoblock
  105.             self.undoblock = 0
  106.             if len(cmd) > 0:
  107.                 if len(cmd) == 1:
  108.                     # no need to wrap a single cmd
  109.                     cmd = cmd.getcmd(0)
  110.                 # this blk of cmds, or single cmd, has already
  111.                 # been done, so don't execute it again
  112.                 self.addcmd(cmd, 0)
  113.  
  114.     def addcmd(self, cmd, execute=True):
  115.         if execute:
  116.             cmd.do(self.delegate)
  117.         if self.undoblock != 0:
  118.             self.undoblock.append(cmd)
  119.             return
  120.         if self.can_merge and self.pointer > 0:
  121.             lastcmd = self.undolist[self.pointer-1]
  122.             if lastcmd.merge(cmd):
  123.                 return
  124.         self.undolist[self.pointer:] = [cmd]
  125.         if self.saved > self.pointer:
  126.             self.saved = -1
  127.         self.pointer = self.pointer + 1
  128.         if len(self.undolist) > self.max_undo:
  129.             ##print "truncating undo list"
  130.             del self.undolist[0]
  131.             self.pointer = self.pointer - 1
  132.             if self.saved >= 0:
  133.                 self.saved = self.saved - 1
  134.         self.can_merge = True
  135.         self.check_saved()
  136.  
  137.     def undo_event(self, event):
  138.         if self.pointer == 0:
  139.             self.bell()
  140.             return "break"
  141.         cmd = self.undolist[self.pointer - 1]
  142.         cmd.undo(self.delegate)
  143.         self.pointer = self.pointer - 1
  144.         self.can_merge = False
  145.         self.check_saved()
  146.         return "break"
  147.  
  148.     def redo_event(self, event):
  149.         if self.pointer >= len(self.undolist):
  150.             self.bell()
  151.             return "break"
  152.         cmd = self.undolist[self.pointer]
  153.         cmd.redo(self.delegate)
  154.         self.pointer = self.pointer + 1
  155.         self.can_merge = False
  156.         self.check_saved()
  157.         return "break"
  158.  
  159.  
  160. class Command:
  161.  
  162.     # Base class for Undoable commands
  163.  
  164.     tags = None
  165.  
  166.     def __init__(self, index1, index2, chars, tags=None):
  167.         self.marks_before = {}
  168.         self.marks_after = {}
  169.         self.index1 = index1
  170.         self.index2 = index2
  171.         self.chars = chars
  172.         if tags:
  173.             self.tags = tags
  174.  
  175.     def __repr__(self):
  176.         s = self.__class__.__name__
  177.         t = (self.index1, self.index2, self.chars, self.tags)
  178.         if self.tags is None:
  179.             t = t[:-1]
  180.         return s + `t`
  181.  
  182.     def do(self, text):
  183.         pass
  184.  
  185.     def redo(self, text):
  186.         pass
  187.  
  188.     def undo(self, text):
  189.         pass
  190.  
  191.     def merge(self, cmd):
  192.         return 0
  193.  
  194.     def save_marks(self, text):
  195.         marks = {}
  196.         for name in text.mark_names():
  197.             if name != "insert" and name != "current":
  198.                 marks[name] = text.index(name)
  199.         return marks
  200.  
  201.     def set_marks(self, text, marks):
  202.         for name, index in marks.items():
  203.             text.mark_set(name, index)
  204.  
  205.  
  206. class InsertCommand(Command):
  207.  
  208.     # Undoable insert command
  209.  
  210.     def __init__(self, index1, chars, tags=None):
  211.         Command.__init__(self, index1, None, chars, tags)
  212.  
  213.     def do(self, text):
  214.         self.marks_before = self.save_marks(text)
  215.         self.index1 = text.index(self.index1)
  216.         if text.compare(self.index1, ">", "end-1c"):
  217.             # Insert before the final newline
  218.             self.index1 = text.index("end-1c")
  219.         text.insert(self.index1, self.chars, self.tags)
  220.         self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
  221.         self.marks_after = self.save_marks(text)
  222.         ##sys.__stderr__.write("do: %s\n" % self)
  223.  
  224.     def redo(self, text):
  225.         text.mark_set('insert', self.index1)
  226.         text.insert(self.index1, self.chars, self.tags)
  227.         self.set_marks(text, self.marks_after)
  228.         text.see('insert')
  229.         ##sys.__stderr__.write("redo: %s\n" % self)
  230.  
  231.     def undo(self, text):
  232.         text.mark_set('insert', self.index1)
  233.         text.delete(self.index1, self.index2)
  234.         self.set_marks(text, self.marks_before)
  235.         text.see('insert')
  236.         ##sys.__stderr__.write("undo: %s\n" % self)
  237.  
  238.     def merge(self, cmd):
  239.         if self.__class__ is not cmd.__class__:
  240.             return False
  241.         if self.index2 != cmd.index1:
  242.             return False
  243.         if self.tags != cmd.tags:
  244.             return False
  245.         if len(cmd.chars) != 1:
  246.             return False
  247.         if self.chars and \
  248.            self.classify(self.chars[-1]) != self.classify(cmd.chars):
  249.             return False
  250.         self.index2 = cmd.index2
  251.         self.chars = self.chars + cmd.chars
  252.         return True
  253.  
  254.     alphanumeric = string.ascii_letters + string.digits + "_"
  255.  
  256.     def classify(self, c):
  257.         if c in self.alphanumeric:
  258.             return "alphanumeric"
  259.         if c == "\n":
  260.             return "newline"
  261.         return "punctuation"
  262.  
  263.  
  264. class DeleteCommand(Command):
  265.  
  266.     # Undoable delete command
  267.  
  268.     def __init__(self, index1, index2=None):
  269.         Command.__init__(self, index1, index2, None, None)
  270.  
  271.     def do(self, text):
  272.         self.marks_before = self.save_marks(text)
  273.         self.index1 = text.index(self.index1)
  274.         if self.index2:
  275.             self.index2 = text.index(self.index2)
  276.         else:
  277.             self.index2 = text.index(self.index1 + " +1c")
  278.         if text.compare(self.index2, ">", "end-1c"):
  279.             # Don't delete the final newline
  280.             self.index2 = text.index("end-1c")
  281.         self.chars = text.get(self.index1, self.index2)
  282.         text.delete(self.index1, self.index2)
  283.         self.marks_after = self.save_marks(text)
  284.         ##sys.__stderr__.write("do: %s\n" % self)
  285.  
  286.     def redo(self, text):
  287.         text.mark_set('insert', self.index1)
  288.         text.delete(self.index1, self.index2)
  289.         self.set_marks(text, self.marks_after)
  290.         text.see('insert')
  291.         ##sys.__stderr__.write("redo: %s\n" % self)
  292.  
  293.     def undo(self, text):
  294.         text.mark_set('insert', self.index1)
  295.         text.insert(self.index1, self.chars)
  296.         self.set_marks(text, self.marks_before)
  297.         text.see('insert')
  298.         ##sys.__stderr__.write("undo: %s\n" % self)
  299.  
  300. class CommandSequence(Command):
  301.  
  302.     # Wrapper for a sequence of undoable cmds to be undone/redone
  303.     # as a unit
  304.  
  305.     def __init__(self):
  306.         self.cmds = []
  307.         self.depth = 0
  308.  
  309.     def __repr__(self):
  310.         s = self.__class__.__name__
  311.         strs = []
  312.         for cmd in self.cmds:
  313.             strs.append("    " + `cmd`)
  314.         return s + "(\n" + ",\n".join(strs) + "\n)"
  315.  
  316.     def __len__(self):
  317.         return len(self.cmds)
  318.  
  319.     def append(self, cmd):
  320.         self.cmds.append(cmd)
  321.  
  322.     def getcmd(self, i):
  323.         return self.cmds[i]
  324.  
  325.     def redo(self, text):
  326.         for cmd in self.cmds:
  327.             cmd.redo(text)
  328.  
  329.     def undo(self, text):
  330.         cmds = self.cmds[:]
  331.         cmds.reverse()
  332.         for cmd in cmds:
  333.             cmd.undo(text)
  334.  
  335.     def bump_depth(self, incr=1):
  336.         self.depth = self.depth + incr
  337.         return self.depth
  338.  
  339. def main():
  340.     from Percolator import Percolator
  341.     root = Tk()
  342.     root.wm_protocol("WM_DELETE_WINDOW", root.quit)
  343.     text = Text()
  344.     text.pack()
  345.     text.focus_set()
  346.     p = Percolator(text)
  347.     d = UndoDelegator()
  348.     p.insertfilter(d)
  349.     root.mainloop()
  350.  
  351. if __name__ == "__main__":
  352.     main()
  353.