Tuesday, July 29, 2008

Undo with PyObjC



Here is the app window for my NSUndoManager demo. It is a document-based application b/c we get the NSUndoManager for free that way, and when I tried getting an instance of it directly (no-document), that didn't work. In my version of the Person class, each new person object has a number and a different expectedRaise. It's all standard.

class Person(NSObject):
expectedRaise = objc.ivar('expectedRaise')
personName = objc.ivar('personName')
count = 1

def init(self):
self.expectedRaise = Person.count * 5.0
self.personName = 'New Person #' + str(Person.count)
Person.count += 1
return self

def setExpectedRaise(self,value):
self.expectedRaise = value

def setPersonName(self,value):
self.personName = value

def description(self):
return self.personName + str(self.expectedRaise).rjust(6)

The buttons are wired to actions as usual. The TextField is bound to an ivar. The ivar "employees" holds the array of person objects. Here is the first part of the document class:


myTextField = objc.ivar('myTextField')
employees = objc.ivar('employees')

def setmyTextField_(self, value):
self.myTextField = value

def init(self):
self = super(PyUndo2Document, self).init()
L = list()
for i in range(2):
p = Person.Person.alloc().init()
L.append(p)
self.employees = NSMutableArray.arrayWithArray_(L)
self.show_(self)
return self

I won't show the standard document methods, but here is my implementation of add_ and remove_, based on Hillegass. "Undo" and "Redo" work correctly. And if we add two persons and then "Undo", the text field updates properly. If we do remove with the button, the text field also updates. But if we then do "Undo delete", the text field does not update. We can see that it has a new value if we press "show". I'm not sure why there is no update.

@objc.IBAction
def add_(self,sender):
print 'add'
undo = self.undoManager()
#undo = NSUndoManager.alloc().init()
inv = undo.prepareWithInvocationTarget_(self)
inv.remove_(self.employees.count())
if not undo.isUndoing():
undo.setActionName_("Insert Person")
self.employees.addObject_(Person.Person.alloc().init())
self.show_(self)

def addObject_(self,p):
self.employees.addObject_(p)

@objc.IBAction
def remove_(self,sender):
print 'remove'
p = self.employees.lastObject()
undo = self.undoManager()
inv = undo.prepareWithInvocationTarget_(self)
inv.addObject_(p)
if not undo.isUndoing():
undo.setActionName_("Delete Person")
self.employees.removeLastObject()
self.show_(self)

@objc.IBAction
def show_(self,sender):
p = self.employees.lastObject()
self.setmyTextField_(p.description())

P.S. According to cocoadev, for a non-document app, we just need to get the undoManager from the window. It works! I set an outlet in the class and do:

undo = self.myWindow.undoManager()