Commits

Ronald Oussoren committed f175a06

- Better NSDictionary abstraction
- Subclassing Python/ObjC hybrid now actuall works
- Moved objc.classnib to AppKit.classnib, and added cmdline tool

  • Participants
  • Parent commits d3a5027

Comments (0)

Files changed (14)

File pyobjc/ChangeLog

 	* Reset '__module__' of classes in the __init__.py of AppKit and 
 	  Foundation. help(AppKit) now works (and generates an lot of
 	  output)
+	* pythonify_c_value("@", ...) now correctly process Class objects.
+	* Modules/objc/class-builder.m: Can't just call 
+	  objc_msgSendSuper({ self->super_class->isa, self }, ...), that
+	  won't work when we are the super-class of self...
+	* lib/objc/test/test_subclass.py: Added testcases for above
+	* Digits are valid characters in Objective-C classnames,
+	  class-builder.m incorrectly assumed they are not.
+	* Lib/objc/_convenience.py: It is now possible to add multiple
+	  convenience methods when an Objective-C selector is present, use
+	  this to provide better wrapping of NSDictionary
+	* Lib/AppKit/classnib.py: Moved from Lib/objc/classnib.py, removed
+	  custom parser and use NSDictionary instead.
+	* Tools/mknibwrapper: Script using AppKit.classnib for generating
+	  a module declaring base classes for classes defined in NIB files.
+	* Examples/{TableModel,iClass}: Use script above
+
 
 2002-10-29  Bill Bumgarner  <bbum@bumbox.local.>
 

File pyobjc/Examples/TableModel/TableModel.py

 from AppKit import NSApplicationMain, NSTableDataSource
 from objc import selector
 import sys
+from nibwrapper import PyModelBase
 
-class PyModel (NSObject, NSTableDataSource):
+class PyModel (PyModelBase, NSTableDataSource):
 	__slots__  = ('rowcount')
 
 	def awakeFromNib(self):

File pyobjc/Examples/TableModel/nibwrapper.py

+# THIS FILE IS GENERATED. DO NOT EDIT!!!
+# Interface classes for using NIB files
+
+from objc import IBOutlet
+from Foundation import NSObject
+
+class PyModelBase (NSObject):
+	"Base class for class 'PyModel'"
+	pass
+

File pyobjc/Examples/TableModel/setup-app.py

 	app_name= 'TableModelPY',
 	main_py = 'TableModel.py',
 	raw = 1,
-	extra_files = ['English.lproj'])
+	extra_files = ['English.lproj'],
+	extra_src = ['nibwrapper.py'])

File pyobjc/Examples/iClass/datasource.py

 from Foundation import NSObject, NSBundle
 from AppKit import NSOutlineViewDataSource, NSTableDataSource
 from objc import selector, class_list, objc_object, IBOutlet
+from nibwrapper import  ClassesDataSourceBase
 
 WRAPPED={}
 class Wrapper (NSObject):
 			framework = framework[:-len('.framework')]
 	return framework
 
-class ClassesDataSource (NSObject, NSOutlineViewDataSource, NSTableDataSource):
+class ClassesDataSource (ClassesDataSourceBase, NSOutlineViewDataSource, NSTableDataSource):
 	__slots__ = ('_classList', '_classTree', '_methodInfo')
 
-	_window = IBOutlet('window')
-	_methodTable = IBOutlet('methodTable')
-	_classTable = IBOutlet('classTable')
-	_searchBox = IBOutlet('searchBox')
-	_classLabel = IBOutlet('classLabel')
-	_frameworkLabel = IBOutlet('frameworkLabel')
-
 	def clearClassInfo(self):
 		print "clearClassInfo"
 		self._methodInfo = []
-		self._methodTable.reloadData()
-		self._window.setTitle_('iClass')
+		self.methodTable.reloadData()
+		self.window.setTitle_('iClass')
 
 
 	def setClassInfo(self, cls):
 		print "setClassInfo for",cls
-		self._window.setTitle_('iClass: %s'%cls.__name__)
-		self._classLabel.setStringValue_(classPath(cls))
+		self.window.setTitle_('iClass: %s'%cls.__name__)
+		self.classLabel.setStringValue_(classPath(cls))
 		print (classPath(cls))
-		self._frameworkLabel.setStringValue_(classBundle(cls))
+		self.frameworkLabel.setStringValue_(classBundle(cls))
 		self._methodInfo = []
 		for nm in dir(cls):
 			meth = getattr(cls, nm)
 			self._methodInfo.append(
 				(meth.selector, nm, meth.signature))
 		self._methodInfo.sort()
-		self._methodTable.reloadData()
+		self.methodTable.reloadData()
 
 	def selectClass_(self, event):
 		print "selectClass:"
-		rowNr = self._classTable.selectedRow()
+		rowNr = self.classTable.selectedRow()
 		if rowNr == -1:
 			self.clearClassInfo()
 		else:
-			item = self._classTable.itemAtRow_(rowNr)
+			item = self.classTable.itemAtRow_(rowNr)
 			item = item.value
 			self.setClassInfo(item)
 
 
 		self.showClass(super)
 		item = WRAPPED[super]
-		rowNr = self._classTable.rowForItem_(item)
-		self._classTable.expandItem_(item)
+		rowNr = self.classTable.rowForItem_(item)
+		self.classTable.expandItem_(item)
 
 
 	def selectClass(self, cls):
 		self.showClass(cls)
 
 		item = WRAPPED[cls]
-		rowNr = self._classTable.rowForItem_(item)
+		rowNr = self.classTable.rowForItem_(item)
 
-		self._classTable.scrollRowToVisible_(rowNr)
-		self._classTable.selectRow_byExtendingSelection_(rowNr, False)
+		self.classTable.scrollRowToVisible_(rowNr)
+		self.classTable.selectRow_byExtendingSelection_(rowNr, False)
 
 
 	def searchClass_(self, event):
 		print "searchClass:"
-		val = self._searchBox.stringValue()
+		val = self.searchBox.stringValue()
 		if not val:
 			return
 		print "Searching", val

File pyobjc/Examples/iClass/nibwrapper.py

+# THIS FILE IS GENERATED. DO NOT EDIT!!!
+# Interface classes for using NIB files
+
+from objc import IBOutlet
+from Foundation import NSObject
+
+class ClassesDataSourceBase (NSObject):
+	"Base class for class 'ClassesDataSource'"
+	classLabel = IBOutlet("classLabel")
+	classTable = IBOutlet("classTable")
+	searchBox = IBOutlet("searchBox")
+	frameworkLabel = IBOutlet("frameworkLabel")
+	methodTable = IBOutlet("methodTable")
+	window = IBOutlet("window")
+
+	def selectClass_(self, sender): pass
+
+	def searchClass_(self, sender): pass
+
+

File pyobjc/Examples/iClass/setup-app.py

 	app_name= 'iClass',
 	main_py = 'main.py',
 	raw = True,
-	extra_src = ['datasource.py'],
+	extra_src = ['datasource.py', 'nibwrapper.py'],
 	extra_files = ['English.lproj'])

File pyobjc/Lib/objc/_convenience.py

 from objc import set_class_extender, selector
 
 CONVENIENCE_METHODS = {}
-
-
-CLASS_METHODS = { }
+CLASS_METHODS = {}
 
 def add_convenience_methods(super_class, name, type_dict):
 	"""
 
 		if CONVENIENCE_METHODS.has_key(sel):
 			v = CONVENIENCE_METHODS[sel]
-			type_dict[v[0]] = v[1]
+			for name, value in v:
+				type_dict[name] = value
 	
 	if CLASS_METHODS.has_key(name):
 		for name, value in CLASS_METHODS[name]:
 	if res == None:
 		raise KeyError, key
 	return res
+def has_key_1(self, key):
+	res = self.objectForKey_(key)
+	return not (res is None)
+def get_1(self, key, dflt=None):
+	res = self.objectForKey_(key)
+	if res is None: 
+		res = dflt
+	return res
+
 def __delitem__1(self, key):
 	self.removeObjectForKey_(key)
 def __setitem__1(self, key, value):
 	return self.hash()
 		
 
-CONVENIENCE_METHODS['objectForKey:'] = ('__getitem__', __getitem__1)
-CONVENIENCE_METHODS['removeObjectForKey:'] = ('__delitem__', __delitem__1)
-CONVENIENCE_METHODS['setObject:forKey:'] = ('__setitem__', __setitem__1)
-CONVENIENCE_METHODS['count'] = ('__len__', __len__1)
-CONVENIENCE_METHODS['description'] = ('__repr__', __repr__)
-CONVENIENCE_METHODS['doesContain:'] = ('__contains__', __contains__)
-CONVENIENCE_METHODS['hash'] = ('__hash__', __hash__)
-CONVENIENCE_METHODS['isEqualTo:'] = ('__eq__', __eq__1)
-CONVENIENCE_METHODS['isEqual:'] = ('__eq__', __eq__2)
-CONVENIENCE_METHODS['isGreaterThan:'] = ('__gt__', __gt__)
-CONVENIENCE_METHODS['isGreaterThanOrEqualTo:'] = ('__ge__', __ge__)
-CONVENIENCE_METHODS['isLessThan:'] = ('__lt__', __lt__)
-CONVENIENCE_METHODS['isLessThanOrEqualTo:'] = ('__le__', __le__)
-CONVENIENCE_METHODS['isNotEqualTo:'] = ('__ne__', __ne__)
-CONVENIENCE_METHODS['lenght'] = ('__len__', __len__2)
-CONVENIENCE_METHODS['objectAtIndex:'] = ('__getitem__', __getitem__2)
-CONVENIENCE_METHODS['removeObjectAtIndex:'] = ('__detitem__', __delitem__2)
-CONVENIENCE_METHODS['replaceObjectAtIndex:withObject:'] = ('__setitem__', __setitem__2)
+CONVENIENCE_METHODS['objectForKey:'] = (('__getitem__', __getitem__1), ('has_key', has_key_1), ('get', get_1))
+CONVENIENCE_METHODS['removeObjectForKey:'] = (('__delitem__', __delitem__1),)
+CONVENIENCE_METHODS['setObject:forKey:'] = (('__setitem__', __setitem__1),)
+CONVENIENCE_METHODS['count'] = (('__len__', __len__1),)
+CONVENIENCE_METHODS['description'] = (('__repr__', __repr__),)
+CONVENIENCE_METHODS['doesContain:'] = (('__contains__', __contains__),)
+CONVENIENCE_METHODS['hash'] = (('__hash__', __hash__),)
+CONVENIENCE_METHODS['isEqualTo:'] = (('__eq__', __eq__1),)
+CONVENIENCE_METHODS['isEqual:'] = (('__eq__', __eq__2),)
+CONVENIENCE_METHODS['isGreaterThan:'] = (('__gt__', __gt__),)
+CONVENIENCE_METHODS['isGreaterThanOrEqualTo:'] = (('__ge__', __ge__),)
+CONVENIENCE_METHODS['isLessThan:'] = (('__lt__', __lt__),)
+CONVENIENCE_METHODS['isLessThanOrEqualTo:'] = (('__le__', __le__),)
+CONVENIENCE_METHODS['isNotEqualTo:'] = (('__ne__', __ne__),)
+CONVENIENCE_METHODS['lenght'] = (('__len__', __len__2),)
+CONVENIENCE_METHODS['objectAtIndex:'] = (('__getitem__', __getitem__2),)
+CONVENIENCE_METHODS['removeObjectAtIndex:'] = (('__detitem__', __delitem__2),)
+CONVENIENCE_METHODS['replaceObjectAtIndex:withObject:'] = (('__setitem__', __setitem__2),)
 
 
 
 # TODO (not all are needed or even possible): 
 #   iter*, update, pop, popitem, setdefault
 # __str__ would be nice (as obj.description()),
-CONVENIENCE_METHODS['keyEnumerator'] = ('keys', mapping_keys)
-CONVENIENCE_METHODS['objectEnumerator'] = ('values', mapping_values)
-CONVENIENCE_METHODS['removeAllObjects'] = ('clear', lambda self: self.removeAllObjects())
-CONVENIENCE_METHODS['dictionaryWithDictionary:'] = ('copy', lambda self: type(self).dictionaryWithDictionary_(self))
+CONVENIENCE_METHODS['keyEnumerator'] = (('keys', mapping_keys),)
+CONVENIENCE_METHODS['objectEnumerator'] = (('values', mapping_values),)
+CONVENIENCE_METHODS['removeAllObjects'] = (('clear', lambda self: self.removeAllObjects()),)
+CONVENIENCE_METHODS['dictionaryWithDictionary:'] = (('copy', lambda self: type(self).dictionaryWithDictionary_(self)),)

File pyobjc/Lib/objc/builder.py

 	assert os.path.exists(dirname(app_name))
 	assert isinstance(main_py, str)
 	assert os.path.exists(main_py)
-	assert isinstance(extra_src, list) or isinstance(extra_src, tuple)
+	assert isinstance(extra_src, (list, tuple))
 	assert reduce(lambda x, y: x and y, 
 		[ os.path.exists(x) for x in extra_src ], True)
 	assert isinstance(extra_files, list) or isinstance(extra_files, tuple)

File pyobjc/Lib/objc/test/test_subclass.py

+import unittest
+
+import objc
+
+# Most usefull systems will at least have 'NSObject'.
+NSObject = objc.lookup_class('NSObject')
+
+
+
+class TestSubclassing(unittest.TestCase):
+
+	def testSubclassOfSubclass(self):
+		class Level1Class (NSObject):
+			def hello(self):
+				return "level1"
+
+		class Level2Class (Level1Class):
+			def hello(self):
+				return "level2"
+
+		obj = Level1Class.alloc().init()
+		v = obj.hello()
+		self.assert_(v == "level1")
+
+		obj = Level2Class.alloc().init()
+		v = obj.hello()
+		self.assert_(v == "level2")
+	
+	def testUniqueNames(self):
+		class SomeClass (NSObject): pass
+
+		try:
+			class SomeClass (NSObject): pass
+
+			assert_(0)
+		except objc.error, msg:
+			assert(str(msg) == "Class already exists in Objective-C runtime")
+
+
+
+	
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest( unittest.makeSuite( TestInstantiation ) )
+
+if __name__ == '__main__':
+    unittest.main( )

File pyobjc/Modules/objc/class-builder.m

 	PyObject*          python_class;
 };
 
-#define IDENT_CHARS "ABCDEFGHIJKLMNOPQSRTUVWXYZabcdefghijklmnopqrstuvwxyz_"
+#define IDENT_CHARS "ABCDEFGHIJKLMNOPQSRTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789"
+
+/*
+ * This function finds the superclass of the class where 'selector' is
+ * overridden using 'currentImp'.
+ *
+ * This is needed to call the correct superclass implementation in case 
+ * of multiple layers of subclassing in Python. If we don't find the 'real'
+ * superclass, a call to 
+ *   'objc_msgSendSuper({ self->isa->super_class, self }, ...)' will just 
+ * transfer back to 'currentImp' if the method was called from a subclass (e.g.
+ * if 'currentImp' is the IMP for the superclass of 'self->isa' instead of the
+ * one from 'self'.
+ *
+ * The 'right' way to do this is by building closures (if done correctly it
+ * would at least be faster), but that requires an external library or low-level
+ * (assembly) trickery.
+ */
+static Class find_real_superclass(Class startAt, SEL selector, 
+		Method (*find_method)(Class, SEL), IMP currentImp)
+{
+	Method m;
+	Class  cur;
+
+	cur = startAt;
+	m = find_method(cur, selector);
+
+	/* Skip to class containing this function */
+	while (m == NULL || m->method_imp != currentImp) {
+		cur = cur->super_class;
+		if (!cur) abort();
+		m = find_method(cur, selector);
+	}
+
+	/* Skip all classes containing this function */
+	while (m != NULL && m->method_imp == currentImp) {
+		cur = cur->super_class;
+		if (!cur) abort();
+		m = find_method(cur, selector);
+	}
+
+	/* We found the 'real' superclass */
+	return cur;
+}
 
 
 /*
 
    pyclass = ((struct class_wrapper*)self)->python_class;
 
-   super.class = ((Class)self)->super_class->isa; 
+   super.class = find_real_superclass((Class)self, @selector(allocWithZone:),
+   	class_getClassMethod, (IMP)class_method_allocWithZone)->isa;
    super.receiver = self;
 
    obj = objc_msgSendSuper(&super, @selector(allocWithZone:), zone); 
 
 	if (obj->ob_refcnt == 0) {
 		/* [super release] */
-		super.class = self->isa->super_class;
+   		super.class = find_real_superclass(self->isa, 
+			@selector(release), class_getInstanceMethod, 
+			(IMP)object_method_release);
 		super.receiver = self;
+
 		self = objc_msgSendSuper(&super, @selector(release)); 
 		return;
 
 	}
 	PyErr_Clear();
 
+
 	/* Check superclass */
+	super.class = find_real_superclass(self->isa, 
+			selector, class_getInstanceMethod, 
+			(IMP)object_method_respondsToSelector);
 	super.receiver = self;
-	super.class = self->isa->super_class;
 
 	res = (int)objc_msgSendSuper(&super, selector, aSelector);
 
 	PyObject*          pymeth;
 
 
+	super.class = find_real_superclass(self->isa, 
+			selector, class_getInstanceMethod, 
+			(IMP)object_method_methodSignatureForSelector);
 	super.receiver = self;
-	super.class    = self->isa->super_class;
 
 	NS_DURING
 		result = objc_msgSendSuper(&super, selector, aSelector);

File pyobjc/Modules/objc/objc-class.m

 		abort();
 	}
 	Py_INCREF(py_class);
+
+	res = ObjC_RegisterClassProxy(objc_class, py_class);
+	if (res != 0) {
+		abort();
+	}
+
 	return res;
 }
 

File pyobjc/Modules/objc/objc-object.m

 static int
 register_proxy(PyObject* proxy_obj) 
 {
-	id objc_obj = ObjCObject_GetObject(proxy_obj);
+	id objc_obj;
 	PyObject* v;
 	PyObject* unregister_proxy;
 	struct unregister_data* data;
 
+	if (ObjCObject_Check(proxy_obj)) {
+		objc_obj = ObjCObject_GetObject(proxy_obj);
+	} else if (ObjCClass_Check(proxy_obj)) {
+		objc_obj = ObjCClass_GetClass(proxy_obj);
+	} else {
+		PyErr_SetString(PyExc_TypeError, 
+			"bad argument for register_proxy");
+		return -1;
+	}
+		
+
 	if (proxy_dict == NULL)  {
 		proxy_dict = PyDict_New();
 		if (proxy_dict == NULL) return -1;
 
 #endif /* PyOBJC_UNIQUE_PROXY */
 
+int ObjC_RegisterClassProxy(Class cls, PyObject* classProxy)
+{
+	return register_proxy(classProxy);
+}
+
 static PyObject*
 object_new(PyTypeObject*  type, PyObject* args, PyObject* kwds)
 {

File pyobjc/Modules/objc/pyobjc.h

 PyObject* ObjCClass_FindSelector(PyObject* cls, SEL selector);
 void 	  ObjCClass_MaybeRescan(PyObject* class);
 int 	  ObjCClass_IsSubClass(Class child, Class parent);
+int 	  ObjC_RegisterClassProxy(Class cls, PyObject* classProxy);
+
 
 
 PyObject* ObjCObject_New(id objc_object);