comparison clients/editor/scripts/undomanager.py @ 255:51cc05d862f2

Merged editor_rewrite branch to trunk. This contains changes that may break compatibility against existing clients. For a list of changes that may affect your client, see: http://wiki.fifengine.de/Changes_to_pychan_and_FIFE_in_editor_rewrite_branch
author cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 08 Jun 2009 16:00:02 +0000
parents
children e893afb4963b
comparison
equal deleted inserted replaced
254:10b5f7f36dd4 255:51cc05d862f2
1 from events.signal import Signal
2 import collections
3 import pdb
4
5 actionAdded = Signal(providing_args=["action"])
6 preUndo = Signal()
7 postUndo = Signal()
8 preRedo = Signal()
9 postRedo = Signal()
10 cleared = Signal()
11 modeChanged = Signal(providing_args=["mode"])
12 changed = Signal()
13
14 class UndoManager:
15 """
16 The undo manager provides advanced undo functionality.
17
18 Add actions with addAction. If you want to add a lot of actions and
19 group them, use startGroup and endGroup. When you undo a group, you will
20 undo all the actions within it.
21
22 If branched mode is enabled, you will not overwrite the redostack when
23 adding an action. Instead, a new branch will be created with the new actions.
24 To navigate in branches, you can use nextBranch and redoBranch.
25
26 Example
27 =======
28 # Init undomanager
29 undomanager = UndoManager()
30
31 def doSomething():
32 # Adds an action to the undomanager
33 undocallback = lambda: doSomethingElse()
34 redocallback = lambda: doSomething()
35 action = UndoObject("Did something", "Something was done somewhere in the program.", "icon.png")
36 undomanager.addAction(action)
37
38 def doLotOfActions():
39 # Starts an actiongroup and adds three actions
40 undomanager.startGroup("Did lot of actions", "Lot of actions was done somewhere in the program")
41 doSomething()
42 doSomething()
43 doSomething()
44 undomanager.endGroup()
45
46 # This will create an actiongroup with three actions, and undo it
47 doLotOfActions()
48 undomanager.undo()
49 """
50
51 def __init__(self, branchedMode = False):
52 self._groups = []
53 self._branched_mode = False
54
55 def warn(msg):
56 print "Warning: ",msg
57 self.first_item = UndoStackItem(UndoObject(None, None))
58 self.first_item.object.name = "First item"
59 self.first_item.object.description = "First item in stack. Placeholder"
60 self.first_item.object.undoCallback = lambda: warn("Tried to undo first item")
61 self.first_item.object.redoCallback = lambda: warn("Tried to redo first item")
62
63 self.current_item = self.first_item
64
65 def startGroup(self, name="", description="", icon=""):
66 """
67 Starts an undogroup. Subsequent items will be added to the group
68 until endGroup is called. Undogroups can be nested.
69
70 name, description and icon are information that can be used by
71 scripts which analyze the undostack.
72 """
73 undogroup = UndoGroup(name, description, icon)
74 self._groups.append(undogroup)
75 return undogroup
76
77 def endGroup(self):
78 """
79 Ends the undogroup.
80 """
81 if len(self._groups) <= 0:
82 print "Warning: UndoManager: No groups to end!"
83 return
84
85 group = self._groups.pop()
86 self.addAction(group)
87
88 def addAction(self, action):
89 """
90 Adds an action to the stack.
91
92 If the redostack is not empty and branchmode is enabed,
93 a new branch will be created. If branchmod is disabled,
94 the redo branch will be cleared.
95 """
96
97 if len(self._groups) > 0:
98 self._groups[len(self._groups)-1].addObject(action)
99 else:
100 stackitem = UndoStackItem(action)
101
102 stackitem.previous = self.current_item
103 if self._branched_mode:
104 stackitem.previous.addBranch(stackitem)
105 else:
106 stackitem.previous.next = stackitem
107
108 self.current_item = stackitem
109
110 actionAdded.send(sender=self, action=action)
111 changed.send(sender=self)
112
113 def clear(self):
114 """
115 Clears the undostack.
116 """
117 self._groups = []
118 self.first_item.clearBranches()
119 self.current_item = self.first_item
120
121 cleared.send(sender=self)
122 changed.send(sender=self)
123
124 # Linear undo
125 def undo(self, amount=1):
126 """ Undo [amount] items """
127 if amount <= 0:
128 return
129
130 preUndo.send(sender=self)
131 for i in range(amount):
132 if self.current_item == self.first_item:
133 print "Warning: UndoManager: Tried to undo non-existing action."
134 break
135
136 self.current_item.object.undo()
137 self.current_item = self.current_item.previous
138
139 postUndo.send(sender=self)
140 changed.send(sender=self)
141
142
143 def redo(self, amount=1):
144 """ Redo [amount] items. """
145 if amount <= 0:
146 return
147
148 preRedo.send(sender=self)
149 for i in range(amount):
150 if self.current_item.next is None:
151 print "Warning: UndoManager: Tried to redo non-existing action."
152 break
153
154 self.current_item = self.current_item.next
155 self.current_item.object.redo()
156
157 postRedo.send(sender=self)
158 changed.send(sender=self)
159
160 def getBranchMode(self):
161 """ Returns true if branch mode is enabled """
162 return self._branched_mode
163
164 def setBranchMode(self, enable):
165 """ Enable or disable branch mode """
166 self._branched_mode = enable
167 changed.send(sender=self)
168
169 def getBranches(self):
170 """ Returns branches from current stack item. """
171 return self.current_item.getBranches()
172 def nextBranch(self):
173 """ Switch to next branch in current item """
174 self.current_item.nextBranch()
175 changed.send(sender=self)
176
177 def previousBranch(self):
178 """ Switch previous branch in current item """
179 self.current_item.previousBranch()
180 changed.send(sender=self)
181
182 class UndoObject:
183 """ UndoObject contains all the information that is needed to undo or redo an action,
184 as well as representation of it.
185
186 ATTRIBUTES
187 ==========
188 - name: Name used by scripts analyzing the undostack to represent this item
189 - description: Description of this item
190 - icon: Icon used to represent this item
191 - redoCallback: Function used to redo this item
192 - undoCallback: Function used to undo
193
194 """
195 def __init__(self, undoCallback, redoCallback, name="", description="", icon=""):
196 self.name = name
197 self.redoCallback = redoCallback
198 self.undoCallback = undoCallback
199 self.description = description
200 self.icon = icon
201
202 self.undone = False
203
204 def undo(self):
205 """ Undoes the action. Do not use directly! """
206 if self.undone is True:
207 print "Tried to undo already undone action!"
208 return
209
210 self.undone = True
211 self.undoCallback()
212
213 def redo(self):
214 """ Redoes the action. Do not use directly! """
215 if self.undone is False:
216 print "Tried to redo already redone action!"
217 return
218
219 self.undone = False
220 self.redoCallback()
221
222 class UndoGroup:
223 """
224 Contains a list of actions. Used to group actions together.
225
226 Use UndoManager.startGroup and UndoManager.endGroup.
227
228 """
229 def __init__(self, name="", description="", icon=""):
230 self.name = name
231 self.description = description
232 self.icon = icon
233
234 self.undoobjects = []
235
236 def addObject(self, object):
237 """ Adds an action to the list """
238 self.undoobjects.append(object)
239
240 def getObjects(self):
241 """ Returns a list of the actions contained """
242 return self.undoobjects
243
244 def undo(self):
245 """ Undoes all actions. """
246 for action in reversed(self.undoobjects):
247 action.undo()
248
249 def redo(self):
250 """ Redoes all actions. """
251 for action in self.undoobjects:
252 action.redo()
253
254 class UndoStackItem:
255 """ Represents an action or actiongroup in the undostack. Do not use directly! """
256 def __init__(self, object):
257 self._branches = []
258 self._currentbranch = -1
259
260 self.parent = None
261 self.object = object
262 self.previous = None
263 self.next = None
264
265 def getBranches(self):
266 """ Returns a list of the branches """
267 return self._branches;
268
269 def addBranch(self, item):
270 """ Adds a branch to the list and sets this item to point to that branch """
271 self._branches.append(item)
272
273 self._currentbranch += 1
274 self.next = self._branches[self._currentbranch]
275 self.next.parent = self
276
277 def nextBranch(self):
278 """ Sets this item to point to next branch """
279 if len(self._branches) <= 0:
280 return
281 self._currentbranch += 1
282 if self._currentbranch >= len(self._branches):
283 self._currentbranch = 0
284 self.next = self._branches[self._currentbranch]
285 changed.send(sender=self)
286
287 def previousBranch(self):
288 """ Sets this item to point to previous branch """
289 if len(self._branches) <= 0:
290 return
291
292 self._currentbranch -= 1
293 if self._currentbranch < 0:
294 self._currentbranch = len(self._branches)-1
295 self.next = self._branches[self._currentbranch]
296 changed.send(sender=self)
297
298 def setBranchIndex(self, index):
299 """ Set this item to point to branches[index] """
300 if index < 0 or index >= len(self._branches):
301 return
302 self._currentbranch = index
303 changed.send(sender=self)
304 self.next = self._branches[self._currentbranch]
305
306 def clearBranches(self):
307 """ Removes all branches """
308 self._branches = []
309 self._currentbranch = -1
310 self._next = None
311 changed.send(sender=self)
312
313 def setBranch(self, branch):
314 """ Set this item to point to branch. Returns True on success. """
315 for b in range(len(self._branches)):
316 if self._branches[b] == branch:
317 self._currentbranch = b
318 self.next = self._branches[self._currentbranch]
319 changed.send(sender=self)
320 return True
321 else:
322 print "Didn't find branch!"
323 return False
324