4 from Delegator
import Delegator
14 #$ event <<dump-undo-state>>
15 #$ win <Control-backslash>
16 #$ unix <Control-backslash>
19 class UndoDelegator(Delegator
):
24 Delegator
.__init
__(self
)
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
)
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
:])
52 self
.undoblock
= 0 # or a CommandSequence instance
55 def set_saved(self
, flag
):
57 self
.saved
= self
.pointer
64 return self
.saved
== self
.pointer
66 saved_change_hook
= None
68 def set_saved_change_hook(self
, hook
):
69 self
.saved_change_hook
= hook
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()
80 def insert(self
, index
, chars
, tags
=None):
81 self
.addcmd(InsertCommand(index
, chars
, tags
))
83 def delete(self
, index1
, index2
=None):
84 self
.addcmd(DeleteCommand(index1
, index2
))
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.
97 def undo_block_start(self
):
98 if self
.undoblock
== 0:
99 self
.undoblock
= CommandSequence()
100 self
.undoblock
.bump_depth()
102 def undo_block_stop(self
):
103 if self
.undoblock
.bump_depth(-1) == 0:
108 # no need to wrap a single cmd
110 # this blk of cmds, or single cmd, has already
111 # been done, so don't execute it again
114 def addcmd(self
, cmd
, execute
=1):
116 cmd
.do(self
.delegate
)
117 if self
.undoblock
!= 0:
118 self
.undoblock
.append(cmd
)
120 if self
.can_merge
and self
.pointer
> 0:
121 lastcmd
= self
.undolist
[self
.pointer
-1]
122 if lastcmd
.merge(cmd
):
124 self
.undolist
[self
.pointer
:] = [cmd
]
125 if self
.saved
> self
.pointer
:
127 self
.pointer
= self
.pointer
+ 1
128 if len(self
.undolist
) > self
.max_undo
:
129 ##print "truncating undo list"
131 self
.pointer
= self
.pointer
- 1
133 self
.saved
= self
.saved
- 1
137 def undo_event(self
, event
):
138 if self
.pointer
== 0:
141 cmd
= self
.undolist
[self
.pointer
- 1]
142 cmd
.undo(self
.delegate
)
143 self
.pointer
= self
.pointer
- 1
148 def redo_event(self
, event
):
149 if self
.pointer
>= len(self
.undolist
):
152 cmd
= self
.undolist
[self
.pointer
]
153 cmd
.redo(self
.delegate
)
154 self
.pointer
= self
.pointer
+ 1
162 # Base class for Undoable commands
166 def __init__(self
, index1
, index2
, chars
, tags
=None):
167 self
.marks_before
= {}
168 self
.marks_after
= {}
176 s
= self
.__class
__.__name
__
177 t
= (self
.index1
, self
.index2
, self
.chars
, self
.tags
)
178 if self
.tags
is None:
185 def redo(self
, text
):
188 def undo(self
, text
):
191 def merge(self
, cmd
):
194 def save_marks(self
, text
):
196 for name
in text
.mark_names():
197 if name
!= "insert" and name
!= "current":
198 marks
[name
] = text
.index(name
)
201 def set_marks(self
, text
, marks
):
202 for name
, index
in marks
.items():
203 text
.mark_set(name
, index
)
206 class InsertCommand(Command
):
208 # Undoable insert command
210 def __init__(self
, index1
, chars
, tags
=None):
211 Command
.__init
__(self
, index1
, None, chars
, tags
)
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)
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
)
229 ##sys.__stderr__.write("redo: %s\n" % self)
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
)
236 ##sys.__stderr__.write("undo: %s\n" % self)
238 def merge(self
, cmd
):
239 if self
.__class
__ is not cmd
.__class
__:
241 if self
.index2
!= cmd
.index1
:
243 if self
.tags
!= cmd
.tags
:
245 if len(cmd
.chars
) != 1:
248 self
.classify(self
.chars
[-1]) != self
.classify(cmd
.chars
):
250 self
.index2
= cmd
.index2
251 self
.chars
= self
.chars
+ cmd
.chars
254 alphanumeric
= string
.ascii_letters
+ string
.digits
+ "_"
256 def classify(self
, c
):
257 if c
in self
.alphanumeric
:
258 return "alphanumeric"
264 class DeleteCommand(Command
):
266 # Undoable delete command
268 def __init__(self
, index1
, index2
=None):
269 Command
.__init
__(self
, index1
, index2
, None, None)
272 self
.marks_before
= self
.save_marks(text
)
273 self
.index1
= text
.index(self
.index1
)
275 self
.index2
= text
.index(self
.index2
)
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)
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
)
291 ##sys.__stderr__.write("redo: %s\n" % self)
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
)
298 ##sys.__stderr__.write("undo: %s\n" % self)
300 class CommandSequence(Command
):
302 # Wrapper for a sequence of undoable cmds to be undone/redone
310 s
= self
.__class
__.__name
__
312 for cmd
in self
.cmds
:
313 strs
.append(" " + `cmd`
)
314 return s
+ "(\n" + ",\n".join(strs
) + "\n)"
317 return len(self
.cmds
)
319 def append(self
, cmd
):
320 self
.cmds
.append(cmd
)
325 def redo(self
, text
):
326 for cmd
in self
.cmds
:
329 def undo(self
, text
):
335 def bump_depth(self
, incr
=1):
336 self
.depth
= self
.depth
+ incr
340 from Percolator
import Percolator
342 root
.wm_protocol("WM_DELETE_WINDOW", root
.quit
)
351 if __name__
== "__main__":