python-2.5.2/win32/Lib/idlelib/UndoDelegator.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     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 + repr(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("    %r" % (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()