blob: 39c8e63466534428a0d39f106d93805264eaad4a [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import sys
2import string
3from Tkinter import *
4from Delegator import Delegator
5
Guido van Rossum504b0bf1999-01-02 21:28:54 +00006#$ 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
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000018
19class UndoDelegator(Delegator):
20
21 max_undo = 1000
22
23 def __init__(self):
24 Delegator.__init__(self)
25 self.reset_undo()
Guido van Rossum504b0bf1999-01-02 21:28:54 +000026
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000027 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.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 = 0
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 def addcmd(self, cmd):
86 cmd.do(self.delegate)
87 if self.can_merge and self.pointer > 0:
88 lastcmd = self.undolist[self.pointer-1]
89 if lastcmd.merge(cmd):
90 return
91 self.undolist[self.pointer:] = [cmd]
92 if self.saved > self.pointer:
93 self.saved = -1
94 self.pointer = self.pointer + 1
95 if len(self.undolist) > self.max_undo:
96 ##print "truncating undo list"
97 del self.undolist[0]
98 self.pointer = self.pointer - 1
99 if self.saved >= 0:
100 self.saved = self.saved - 1
101 self.can_merge = 1
102 self.check_saved()
103
104 def undo_event(self, event):
105 if self.pointer == 0:
106 self.bell()
107 return "break"
108 cmd = self.undolist[self.pointer - 1]
109 cmd.undo(self.delegate)
110 self.pointer = self.pointer - 1
111 self.can_merge = 0
112 self.check_saved()
113 return "break"
114
115 def redo_event(self, event):
116 if self.pointer >= len(self.undolist):
117 self.bell()
118 return "break"
119 cmd = self.undolist[self.pointer]
120 cmd.redo(self.delegate)
121 self.pointer = self.pointer + 1
122 self.can_merge = 0
123 self.check_saved()
124 return "break"
125
126
127class Command:
128
129 # Base class for Undoable commands
130
131 tags = None
132
133 def __init__(self, index1, index2, chars, tags=None):
134 self.marks_before = {}
135 self.marks_after = {}
136 self.index1 = index1
137 self.index2 = index2
138 self.chars = chars
139 if tags:
140 self.tags = tags
141
142 def __repr__(self):
143 s = self.__class__.__name__
144 t = (self.index1, self.index2, self.chars, self.tags)
145 if self.tags is None:
146 t = t[:-1]
147 return s + `t`
148
149 def do(self, text):
150 pass
151
152 def redo(self, text):
153 pass
154
155 def undo(self, text):
156 pass
157
158 def merge(self, cmd):
159 return 0
160
161 def save_marks(self, text):
162 marks = {}
163 for name in text.mark_names():
164 if name != "insert" and name != "current":
165 marks[name] = text.index(name)
166 return marks
167
168 def set_marks(self, text, marks):
169 for name, index in marks.items():
170 text.mark_set(name, index)
171
172
173class InsertCommand(Command):
174
175 # Undoable insert command
176
177 def __init__(self, index1, chars, tags=None):
178 Command.__init__(self, index1, None, chars, tags)
179
180 def do(self, text):
181 self.marks_before = self.save_marks(text)
182 self.index1 = text.index(self.index1)
183 if text.compare(self.index1, ">", "end-1c"):
184 # Insert before the final newline
185 self.index1 = text.index("end-1c")
186 text.insert(self.index1, self.chars, self.tags)
187 self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
188 self.marks_after = self.save_marks(text)
189 ##sys.__stderr__.write("do: %s\n" % self)
190
191 def redo(self, text):
192 text.mark_set('insert', self.index1)
193 text.insert(self.index1, self.chars, self.tags)
194 self.set_marks(text, self.marks_after)
195 text.see('insert')
196 ##sys.__stderr__.write("redo: %s\n" % self)
197
198 def undo(self, text):
199 text.mark_set('insert', self.index1)
200 text.delete(self.index1, self.index2)
201 self.set_marks(text, self.marks_before)
202 text.see('insert')
203 ##sys.__stderr__.write("undo: %s\n" % self)
204
205 def merge(self, cmd):
206 if self.__class__ is not cmd.__class__:
207 return 0
208 if self.index2 != cmd.index1:
209 return 0
210 if self.tags != cmd.tags:
211 return 0
212 if len(cmd.chars) != 1:
213 return 0
214 if self.chars and \
215 self.classify(self.chars[-1]) != self.classify(cmd.chars):
216 return 0
217 self.index2 = cmd.index2
218 self.chars = self.chars + cmd.chars
219 return 1
220
221 alphanumeric = string.letters + string.digits + "_"
222
223 def classify(self, c):
224 if c in self.alphanumeric:
225 return "alphanumeric"
226 if c == "\n":
227 return "newline"
228 return "punctuation"
229
230
231class DeleteCommand(Command):
232
233 # Undoable delete command
234
235 def __init__(self, index1, index2=None):
236 Command.__init__(self, index1, index2, None, None)
237
238 def do(self, text):
239 self.marks_before = self.save_marks(text)
240 self.index1 = text.index(self.index1)
241 if self.index2:
242 self.index2 = text.index(self.index2)
243 else:
244 self.index2 = text.index(self.index1 + " +1c")
245 if text.compare(self.index2, ">", "end-1c"):
246 # Don't delete the final newline
247 self.index2 = text.index("end-1c")
248 self.chars = text.get(self.index1, self.index2)
249 text.delete(self.index1, self.index2)
250 self.marks_after = self.save_marks(text)
251 ##sys.__stderr__.write("do: %s\n" % self)
252
253 def redo(self, text):
254 text.mark_set('insert', self.index1)
255 text.delete(self.index1, self.index2)
256 self.set_marks(text, self.marks_after)
257 text.see('insert')
258 ##sys.__stderr__.write("redo: %s\n" % self)
259
260 def undo(self, text):
261 text.mark_set('insert', self.index1)
262 text.insert(self.index1, self.chars)
263 self.set_marks(text, self.marks_before)
264 text.see('insert')
265 ##sys.__stderr__.write("undo: %s\n" % self)
266
267
268def main():
269 from Percolator import Percolator
270 root = Tk()
271 root.wm_protocol("WM_DELETE_WINDOW", root.quit)
272 text = Text()
273 text.pack()
274 text.focus_set()
275 p = Percolator(text)
276 d = UndoDelegator()
277 p.insertfilter(d)
278 root.mainloop()
279
280if __name__ == "__main__":
281 main()