Mercurial > fife-parpg
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/clients/editor/scripts/undomanager.py Mon Jun 08 16:00:02 2009 +0000 @@ -0,0 +1,324 @@ +from events.signal import Signal +import collections +import pdb + +actionAdded = Signal(providing_args=["action"]) +preUndo = Signal() +postUndo = Signal() +preRedo = Signal() +postRedo = Signal() +cleared = Signal() +modeChanged = Signal(providing_args=["mode"]) +changed = Signal() + +class UndoManager: + """ + The undo manager provides advanced undo functionality. + + Add actions with addAction. If you want to add a lot of actions and + group them, use startGroup and endGroup. When you undo a group, you will + undo all the actions within it. + + If branched mode is enabled, you will not overwrite the redostack when + adding an action. Instead, a new branch will be created with the new actions. + To navigate in branches, you can use nextBranch and redoBranch. + + Example + ======= + # Init undomanager + undomanager = UndoManager() + + def doSomething(): + # Adds an action to the undomanager + undocallback = lambda: doSomethingElse() + redocallback = lambda: doSomething() + action = UndoObject("Did something", "Something was done somewhere in the program.", "icon.png") + undomanager.addAction(action) + + def doLotOfActions(): + # Starts an actiongroup and adds three actions + undomanager.startGroup("Did lot of actions", "Lot of actions was done somewhere in the program") + doSomething() + doSomething() + doSomething() + undomanager.endGroup() + + # This will create an actiongroup with three actions, and undo it + doLotOfActions() + undomanager.undo() + """ + + def __init__(self, branchedMode = False): + self._groups = [] + self._branched_mode = False + + def warn(msg): + print "Warning: ",msg + self.first_item = UndoStackItem(UndoObject(None, None)) + self.first_item.object.name = "First item" + self.first_item.object.description = "First item in stack. Placeholder" + self.first_item.object.undoCallback = lambda: warn("Tried to undo first item") + self.first_item.object.redoCallback = lambda: warn("Tried to redo first item") + + self.current_item = self.first_item + + def startGroup(self, name="", description="", icon=""): + """ + Starts an undogroup. Subsequent items will be added to the group + until endGroup is called. Undogroups can be nested. + + name, description and icon are information that can be used by + scripts which analyze the undostack. + """ + undogroup = UndoGroup(name, description, icon) + self._groups.append(undogroup) + return undogroup + + def endGroup(self): + """ + Ends the undogroup. + """ + if len(self._groups) <= 0: + print "Warning: UndoManager: No groups to end!" + return + + group = self._groups.pop() + self.addAction(group) + + def addAction(self, action): + """ + Adds an action to the stack. + + If the redostack is not empty and branchmode is enabed, + a new branch will be created. If branchmod is disabled, + the redo branch will be cleared. + """ + + if len(self._groups) > 0: + self._groups[len(self._groups)-1].addObject(action) + else: + stackitem = UndoStackItem(action) + + stackitem.previous = self.current_item + if self._branched_mode: + stackitem.previous.addBranch(stackitem) + else: + stackitem.previous.next = stackitem + + self.current_item = stackitem + + actionAdded.send(sender=self, action=action) + changed.send(sender=self) + + def clear(self): + """ + Clears the undostack. + """ + self._groups = [] + self.first_item.clearBranches() + self.current_item = self.first_item + + cleared.send(sender=self) + changed.send(sender=self) + + # Linear undo + def undo(self, amount=1): + """ Undo [amount] items """ + if amount <= 0: + return + + preUndo.send(sender=self) + for i in range(amount): + if self.current_item == self.first_item: + print "Warning: UndoManager: Tried to undo non-existing action." + break + + self.current_item.object.undo() + self.current_item = self.current_item.previous + + postUndo.send(sender=self) + changed.send(sender=self) + + + def redo(self, amount=1): + """ Redo [amount] items. """ + if amount <= 0: + return + + preRedo.send(sender=self) + for i in range(amount): + if self.current_item.next is None: + print "Warning: UndoManager: Tried to redo non-existing action." + break + + self.current_item = self.current_item.next + self.current_item.object.redo() + + postRedo.send(sender=self) + changed.send(sender=self) + + def getBranchMode(self): + """ Returns true if branch mode is enabled """ + return self._branched_mode + + def setBranchMode(self, enable): + """ Enable or disable branch mode """ + self._branched_mode = enable + changed.send(sender=self) + + def getBranches(self): + """ Returns branches from current stack item. """ + return self.current_item.getBranches() + def nextBranch(self): + """ Switch to next branch in current item """ + self.current_item.nextBranch() + changed.send(sender=self) + + def previousBranch(self): + """ Switch previous branch in current item """ + self.current_item.previousBranch() + changed.send(sender=self) + +class UndoObject: + """ UndoObject contains all the information that is needed to undo or redo an action, + as well as representation of it. + + ATTRIBUTES + ========== + - name: Name used by scripts analyzing the undostack to represent this item + - description: Description of this item + - icon: Icon used to represent this item + - redoCallback: Function used to redo this item + - undoCallback: Function used to undo + + """ + def __init__(self, undoCallback, redoCallback, name="", description="", icon=""): + self.name = name + self.redoCallback = redoCallback + self.undoCallback = undoCallback + self.description = description + self.icon = icon + + self.undone = False + + def undo(self): + """ Undoes the action. Do not use directly! """ + if self.undone is True: + print "Tried to undo already undone action!" + return + + self.undone = True + self.undoCallback() + + def redo(self): + """ Redoes the action. Do not use directly! """ + if self.undone is False: + print "Tried to redo already redone action!" + return + + self.undone = False + self.redoCallback() + +class UndoGroup: + """ + Contains a list of actions. Used to group actions together. + + Use UndoManager.startGroup and UndoManager.endGroup. + + """ + def __init__(self, name="", description="", icon=""): + self.name = name + self.description = description + self.icon = icon + + self.undoobjects = [] + + def addObject(self, object): + """ Adds an action to the list """ + self.undoobjects.append(object) + + def getObjects(self): + """ Returns a list of the actions contained """ + return self.undoobjects + + def undo(self): + """ Undoes all actions. """ + for action in reversed(self.undoobjects): + action.undo() + + def redo(self): + """ Redoes all actions. """ + for action in self.undoobjects: + action.redo() + +class UndoStackItem: + """ Represents an action or actiongroup in the undostack. Do not use directly! """ + def __init__(self, object): + self._branches = [] + self._currentbranch = -1 + + self.parent = None + self.object = object + self.previous = None + self.next = None + + def getBranches(self): + """ Returns a list of the branches """ + return self._branches; + + def addBranch(self, item): + """ Adds a branch to the list and sets this item to point to that branch """ + self._branches.append(item) + + self._currentbranch += 1 + self.next = self._branches[self._currentbranch] + self.next.parent = self + + def nextBranch(self): + """ Sets this item to point to next branch """ + if len(self._branches) <= 0: + return + self._currentbranch += 1 + if self._currentbranch >= len(self._branches): + self._currentbranch = 0 + self.next = self._branches[self._currentbranch] + changed.send(sender=self) + + def previousBranch(self): + """ Sets this item to point to previous branch """ + if len(self._branches) <= 0: + return + + self._currentbranch -= 1 + if self._currentbranch < 0: + self._currentbranch = len(self._branches)-1 + self.next = self._branches[self._currentbranch] + changed.send(sender=self) + + def setBranchIndex(self, index): + """ Set this item to point to branches[index] """ + if index < 0 or index >= len(self._branches): + return + self._currentbranch = index + changed.send(sender=self) + self.next = self._branches[self._currentbranch] + + def clearBranches(self): + """ Removes all branches """ + self._branches = [] + self._currentbranch = -1 + self._next = None + changed.send(sender=self) + + def setBranch(self, branch): + """ Set this item to point to branch. Returns True on success. """ + for b in range(len(self._branches)): + if self._branches[b] == branch: + self._currentbranch = b + self.next = self._branches[self._currentbranch] + changed.send(sender=self) + return True + else: + print "Didn't find branch!" + return False + \ No newline at end of file