blob: ee49651dc9edcd7ea07e70fc50fec827382dab8a [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import sys
2import string
3from Tkinter import *
4from Delegator import Delegator
5
6
7class UndoDelegator(Delegator):
8
9 max_undo = 1000
10
11 def __init__(self):
12 Delegator.__init__(self)
13 self.reset_undo()
14
15 def setdelegate(self, delegate):
16 if self.delegate is not None:
17 self.unbind("<<undo>>")
18 self.unbind("<<redo>>")
19 self.unbind("<<dump-undo-state>>")
20 Delegator.setdelegate(self, delegate)
21 if delegate is not None:
22 self.bind("<<undo>>", self.undo_event)
23 self.bind("<<redo>>", self.redo_event)
24 self.bind("<<dump-undo-state>>", self.dump_event)
25
26 def dump_event(self, event):
27 from pprint import pprint
28 pprint(self.undolist[:self.pointer])
29 print "pointer:", self.pointer,
30 print "saved:", self.saved,
31 print "can_merge:", self.can_merge,
32 print "get_saved():", self.get_saved()
33 pprint(self.undolist[self.pointer:])
34 return "break"
35
36 def reset_undo(self):
37 self.was_saved = -1
38 self.pointer = 0
39 self.undolist = []
40 self.set_saved(1)
41
42 def set_saved(self, flag):
43 if flag:
44 self.saved = self.pointer
45 else:
46 self.saved = -1
47 self.can_merge = 0
48 self.check_saved()
49
50 def get_saved(self):
51 return self.saved == self.pointer
52
53 saved_change_hook = None
54
55 def set_saved_change_hook(self, hook):
56 self.saved_change_hook = hook
57
58 was_saved = -1
59
60 def check_saved(self):
61 is_saved = self.get_saved()
62 if is_saved != self.was_saved:
63 self.was_saved = is_saved
64 if self.saved_change_hook:
65 self.saved_change_hook()
66
67 def insert(self, index, chars, tags=None):
68 self.addcmd(InsertCommand(index, chars, tags))
69
70 def delete(self, index1, index2=None):
71 self.addcmd(DeleteCommand(index1, index2))
72
73 def addcmd(self, cmd):
74 cmd.do(self.delegate)
75 if self.can_merge and self.pointer > 0:
76 lastcmd = self.undolist[self.pointer-1]
77 if lastcmd.merge(cmd):
78 return
79 self.undolist[self.pointer:] = [cmd]
80 if self.saved > self.pointer:
81 self.saved = -1
82 self.pointer = self.pointer + 1
83 if len(self.undolist) > self.max_undo:
84 ##print "truncating undo list"
85 del self.undolist[0]
86 self.pointer = self.pointer - 1
87 if self.saved >= 0:
88 self.saved = self.saved - 1
89 self.can_merge = 1
90 self.check_saved()
91
92 def undo_event(self, event):
93 if self.pointer == 0:
94 self.bell()
95 return "break"
96 cmd = self.undolist[self.pointer - 1]
97 cmd.undo(self.delegate)
98 self.pointer = self.pointer - 1
99 self.can_merge = 0
100 self.check_saved()
101 return "break"
102
103 def redo_event(self, event):
104 if self.pointer >= len(self.undolist):
105 self.bell()
106 return "break"
107 cmd = self.undolist[self.pointer]
108 cmd.redo(self.delegate)
109 self.pointer = self.pointer + 1
110 self.can_merge = 0
111 self.check_saved()
112 return "break"
113
114
115class Command:
116
117 # Base class for Undoable commands
118
119 tags = None
120
121 def __init__(self, index1, index2, chars, tags=None):
122 self.marks_before = {}
123 self.marks_after = {}
124 self.index1 = index1
125 self.index2 = index2
126 self.chars = chars
127 if tags:
128 self.tags = tags
129
130 def __repr__(self):
131 s = self.__class__.__name__
132 t = (self.index1, self.index2, self.chars, self.tags)
133 if self.tags is None:
134 t = t[:-1]
135 return s + `t`
136
137 def do(self, text):
138 pass
139
140 def redo(self, text):
141 pass
142
143 def undo(self, text):
144 pass
145
146 def merge(self, cmd):
147 return 0
148
149 def save_marks(self, text):
150 marks = {}
151 for name in text.mark_names():
152 if name != "insert" and name != "current":
153 marks[name] = text.index(name)
154 return marks
155
156 def set_marks(self, text, marks):
157 for name, index in marks.items():
158 text.mark_set(name, index)
159
160
161class InsertCommand(Command):
162
163 # Undoable insert command
164
165 def __init__(self, index1, chars, tags=None):
166 Command.__init__(self, index1, None, chars, tags)
167
168 def do(self, text):
169 self.marks_before = self.save_marks(text)
170 self.index1 = text.index(self.index1)
171 if text.compare(self.index1, ">", "end-1c"):
172 # Insert before the final newline
173 self.index1 = text.index("end-1c")
174 text.insert(self.index1, self.chars, self.tags)
175 self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
176 self.marks_after = self.save_marks(text)
177 ##sys.__stderr__.write("do: %s\n" % self)
178
179 def redo(self, text):
180 text.mark_set('insert', self.index1)
181 text.insert(self.index1, self.chars, self.tags)
182 self.set_marks(text, self.marks_after)
183 text.see('insert')
184 ##sys.__stderr__.write("redo: %s\n" % self)
185
186 def undo(self, text):
187 text.mark_set('insert', self.index1)
188 text.delete(self.index1, self.index2)
189 self.set_marks(text, self.marks_before)
190 text.see('insert')
191 ##sys.__stderr__.write("undo: %s\n" % self)
192
193 def merge(self, cmd):
194 if self.__class__ is not cmd.__class__:
195 return 0
196 if self.index2 != cmd.index1:
197 return 0
198 if self.tags != cmd.tags:
199 return 0
200 if len(cmd.chars) != 1:
201 return 0
202 if self.chars and \
203 self.classify(self.chars[-1]) != self.classify(cmd.chars):
204 return 0
205 self.index2 = cmd.index2
206 self.chars = self.chars + cmd.chars
207 return 1
208
209 alphanumeric = string.letters + string.digits + "_"
210
211 def classify(self, c):
212 if c in self.alphanumeric:
213 return "alphanumeric"
214 if c == "\n":
215 return "newline"
216 return "punctuation"
217
218
219class DeleteCommand(Command):
220
221 # Undoable delete command
222
223 def __init__(self, index1, index2=None):
224 Command.__init__(self, index1, index2, None, None)
225
226 def do(self, text):
227 self.marks_before = self.save_marks(text)
228 self.index1 = text.index(self.index1)
229 if self.index2:
230 self.index2 = text.index(self.index2)
231 else:
232 self.index2 = text.index(self.index1 + " +1c")
233 if text.compare(self.index2, ">", "end-1c"):
234 # Don't delete the final newline
235 self.index2 = text.index("end-1c")
236 self.chars = text.get(self.index1, self.index2)
237 text.delete(self.index1, self.index2)
238 self.marks_after = self.save_marks(text)
239 ##sys.__stderr__.write("do: %s\n" % self)
240
241 def redo(self, text):
242 text.mark_set('insert', self.index1)
243 text.delete(self.index1, self.index2)
244 self.set_marks(text, self.marks_after)
245 text.see('insert')
246 ##sys.__stderr__.write("redo: %s\n" % self)
247
248 def undo(self, text):
249 text.mark_set('insert', self.index1)
250 text.insert(self.index1, self.chars)
251 self.set_marks(text, self.marks_before)
252 text.see('insert')
253 ##sys.__stderr__.write("undo: %s\n" % self)
254
255
256def main():
257 from Percolator import Percolator
258 root = Tk()
259 root.wm_protocol("WM_DELETE_WINDOW", root.quit)
260 text = Text()
261 text.pack()
262 text.focus_set()
263 p = Percolator(text)
264 d = UndoDelegator()
265 p.insertfilter(d)
266 root.mainloop()
267
268if __name__ == "__main__":
269 main()