comparison tools/editor/scripts/undomanager.py @ 378:64738befdf3b

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