symbian-qemu-0.9.1-12/python-2.6.1/Lib/idlelib/UndoDelegator.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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()