home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 June / maximum-cd-2011-06.iso / DiscContents / LibO_3.3.1_Win_x86_install_multi.exe / libreoffice1.cab / UndoDelegator.py < prev    next >
Encoding:
Python Source  |  2011-02-15  |  10.0 KB  |  352 lines

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