Donovan Preston's patch #538395, with some mods by me.

This patch makes inheritance for OSA classes work. The implementation is a
bit convoluted, but I don't immedeately see a simpler way of doing it.

I added calls to ascii() everywhere we output strings that may contain
non-ascii characters (Python has gotten very picky since the encoding
patch:-).

I also removed Donovan's different way of opening resource files: I don't
seem to need it.
diff --git a/Mac/Lib/aepack.py b/Mac/Lib/aepack.py
index f58dd33..dfb2953 100644
--- a/Mac/Lib/aepack.py
+++ b/Mac/Lib/aepack.py
@@ -24,6 +24,7 @@
 import StringIO
 import aetypes
 from aetypes import mkenum, mktype
+import os
 
 # These ones seem to be missing from AppleEvents
 # (they're in AERegistry.h)
@@ -61,6 +62,15 @@
 FSSType = macfs.FSSpecType
 AliasType = macfs.AliasType
 
+def packkey(ae, key, value):
+	if hasattr(key, 'which'):
+		keystr = key.which
+	elif hasattr(key, 'want'):
+		keystr = key.want
+	else:
+		keystr = key
+	ae.AEPutParamDesc(keystr, pack(value))
+
 def pack(x, forcetype = None):
 	"""Pack a python object into an AE descriptor"""
 	
@@ -99,13 +109,18 @@
 	if t == DictionaryType:
 		record = AE.AECreateList('', 1)
 		for key, value in x.items():
-			record.AEPutParamDesc(key, pack(value))
+			packkey(record, key, value)
+			#record.AEPutParamDesc(key, pack(value))
 		return record
 	if t == InstanceType and hasattr(x, '__aepack__'):
 		return x.__aepack__()
+	if hasattr(x, 'which'):
+		return AE.AECreateDesc('TEXT', x.which)
+	if hasattr(x, 'want'):
+		return AE.AECreateDesc('TEXT', x.want)
 	return AE.AECreateDesc('TEXT', repr(x)) # Copout
 
-def unpack(desc):
+def unpack(desc, formodulename=""):
 	"""Unpack an AE descriptor to a python object"""
 	t = desc.type
 	
@@ -117,17 +132,17 @@
 		l = []
 		for i in range(desc.AECountItems()):
 			keyword, item = desc.AEGetNthDesc(i+1, '****')
-			l.append(unpack(item))
+			l.append(unpack(item, formodulename))
 		return l
 	if t == typeAERecord:
 		d = {}
 		for i in range(desc.AECountItems()):
 			keyword, item = desc.AEGetNthDesc(i+1, '****')
-			d[keyword] = unpack(item)
+			d[keyword] = unpack(item, formodulename)
 		return d
 	if t == typeAEText:
 		record = desc.AECoerceDesc('reco')
-		return mkaetext(unpack(record))
+		return mkaetext(unpack(record, formodulename))
 	if t == typeAlias:
 		return macfs.RawAlias(desc.data)
 	# typeAppleEvent returned as unknown
@@ -153,7 +168,7 @@
 		return macfs.RawFSSpec(desc.data)
 	if t == typeInsertionLoc:
 		record = desc.AECoerceDesc('reco')
-		return mkinsertionloc(unpack(record))
+		return mkinsertionloc(unpack(record, formodulename))
 	# typeInteger equal to typeLongInteger
 	if t == typeIntlText:
 		script, language = struct.unpack('hh', desc.data[:4])
@@ -177,7 +192,11 @@
 		return v
 	if t == typeObjectSpecifier:
 		record = desc.AECoerceDesc('reco')
-		return mkobject(unpack(record))
+		# If we have been told the name of the module we are unpacking aedescs for,
+		# we can attempt to create the right type of python object from that module.
+		if formodulename:
+			return mkobjectfrommodule(unpack(record, formodulename), formodulename)
+		return mkobject(unpack(record, formodulename))
 	# typePict returned as unknown
 	# typePixelMap coerced to typeAERecord
 	# typePixelMapMinus returned as unknown
@@ -214,13 +233,13 @@
 	#
 	if t == 'rang':
 		record = desc.AECoerceDesc('reco')
-		return mkrange(unpack(record))
+		return mkrange(unpack(record, formodulename))
 	if t == 'cmpd':
 		record = desc.AECoerceDesc('reco')
-		return mkcomparison(unpack(record))
+		return mkcomparison(unpack(record, formodulename))
 	if t == 'logi':
 		record = desc.AECoerceDesc('reco')
-		return mklogical(unpack(record))
+		return mklogical(unpack(record, formodulename))
 	return mkunknown(desc.type, desc.data)
 	
 def coerce(data, egdata):
@@ -311,6 +330,20 @@
 		return aetypes.Property(seld.type, fr)
 	return aetypes.ObjectSpecifier(want, form, seld, fr)
 
+# Note by Jack: I'm not 100% sure of the following code. This was
+# provided by Donovan Preston, but I wonder whether the assignment
+# to __class__ is safe. Moreover, shouldn't there be a better
+# initializer for the classes in the suites?
+def mkobjectfrommodule(dict, modulename):
+	want = dict['want'].type
+	module = __import__(modulename)
+	codenamemapper = module._classdeclarations
+	classtype = codenamemapper.get(want, None)
+	newobj = mkobject(dict)
+	if classtype:
+		newobj.__class__ = classtype
+	return newobj
+
 def _test():
 	"""Test program. Pack and unpack various things"""
 	objs = [
diff --git a/Mac/Lib/aetools.py b/Mac/Lib/aetools.py
index ba42f03..5495dfa 100644
--- a/Mac/Lib/aetools.py
+++ b/Mac/Lib/aetools.py
@@ -28,7 +28,7 @@
 import sys
 
 from aetypes import *
-from aepack import pack, unpack, coerce, AEDescType
+from aepack import packkey, pack, unpack, coerce, AEDescType
 
 Error = 'aetools.Error'
 
@@ -56,19 +56,19 @@
 		return None
 	return desc.data
 
-def unpackevent(ae):
+def unpackevent(ae, formodulename=""):
 	parameters = {}
 	try:
 		dirobj = ae.AEGetParamDesc('----', '****')
 	except AE.Error:
 		pass
 	else:
-		parameters['----'] = unpack(dirobj)
+		parameters['----'] = unpack(dirobj, formodulename)
 		del dirobj
 	while 1:
 		key = missed(ae)
 		if not key: break
-		parameters[key] = unpack(ae.AEGetParamDesc(key, '****'))
+		parameters[key] = unpack(ae.AEGetParamDesc(key, '****'), formodulename)
 	attributes = {}
 	for key in aekeywords:
 		try:
@@ -77,14 +77,14 @@
 			if msg[0] != -1701 and msg[0] != -1704:
 				raise sys.exc_type, sys.exc_value
 			continue
-		attributes[key] = unpack(desc)
+		attributes[key] = unpack(desc, formodulename)
 	return parameters, attributes
 
 def packevent(ae, parameters = {}, attributes = {}):
 	for key, value in parameters.items():
-		ae.AEPutParamDesc(key, pack(value))
+		packkey(ae, key, value)
 	for key, value in attributes.items():
-		ae.AEPutAttributeDesc(key, pack(value))
+		packkey(ae, key, value)
 
 #
 # Support routine for automatically generated Suite interfaces
@@ -130,6 +130,7 @@
 class TalkTo:
 	"""An AE connection to an application"""
 	_signature = None	# Can be overridden by subclasses
+	_moduleName = None # Can be overridden by subclasses
 	
 	def __init__(self, signature=None, start=0, timeout=0):
 		"""Create a communication channel with a particular application.
@@ -183,7 +184,7 @@
 		
 		reply = event.AESend(self.send_flags, self.send_priority,
 		                          self.send_timeout)
-		parameters, attributes = unpackevent(reply)
+		parameters, attributes = unpackevent(reply, self._moduleName)
 		return reply, parameters, attributes
 		
 	def send(self, code, subcode, parameters = {}, attributes = {}):
@@ -218,6 +219,29 @@
 
 		if _arguments.has_key('----'):
 			return _arguments['----']
+			if as:
+				item.__class__ = as
+			return item
+
+	def _set(self, _object, _arguments = {}, _attributes = {}):
+		""" _set: set data for an object
+		Required argument: the object
+		Keyword argument _parameters: Parameter dictionary for the set operation
+		Keyword argument _attributes: AppleEvent attribute dictionary
+		Returns: the data
+		"""
+		_code = 'core'
+		_subcode = 'setd'
+		
+		_arguments['----'] = _object
+
+		_reply, _arguments, _attributes = self.send(_code, _subcode,
+				_arguments, _attributes)
+		if _arguments.has_key('errn'):
+			raise Error, decodeerror(_arguments)
+
+		if _arguments.has_key('----'):
+			return _arguments['----']
 
 # Tiny Finder class, for local use only
 
diff --git a/Mac/Lib/aetypes.py b/Mac/Lib/aetypes.py
index 8fe8cf9..d376e74 100644
--- a/Mac/Lib/aetypes.py
+++ b/Mac/Lib/aetypes.py
@@ -9,9 +9,9 @@
 # convoluted, since there are cyclic dependencies between this file and
 # aetools_convert.
 #
-def pack(*args):
+def pack(*args, **kwargs):
 	from aepack import pack
-	return apply(pack, args)
+	return apply(pack, args, kwargs)
 	
 def IsSubclass(cls, base):
 	"""Test whether CLASS1 is the same as or a subclass of CLASS2"""
@@ -69,6 +69,26 @@
 	if IsEnum(enum): return enum
 	return Enum(enum)
 
+# Jack changed the way this is done
+class InsertionLoc:
+	def __init__(self, of, pos):
+		self.of = of
+		self.pos = pos
+	
+	def __repr__(self):
+		return "InsertionLoc(%s, %s)" % (`self.of`, `self.pos`)
+		
+	def __aepack__(self):
+		rec = {'kobj': self.of, 'kpos': self.pos}
+		return pack(rec, forcetype='insl')
+		
+# Convenience functions for dsp:
+def beginning(of):
+	return InsertionLoc(of, Enum('bgng'))
+	
+def end(of):
+	return InsertionLoc(of, Enum('end '))
+
 class Boolean:
 	"""An AE boolean value"""
 	
diff --git a/Mac/scripts/gensuitemodule.py b/Mac/scripts/gensuitemodule.py
index 3e23661..6b17a79 100644
--- a/Mac/scripts/gensuitemodule.py
+++ b/Mac/scripts/gensuitemodule.py
@@ -15,6 +15,7 @@
 import macfs
 import keyword
 import macresource
+from aetools import unpack
 
 from Carbon.Res import *
 
@@ -274,7 +275,8 @@
 	fss.SetCreatorType('Pyth', 'TEXT')
 	fp.write('"""\n')
 	fp.write("Package generated from %s\n"%fname)
-	fp.write("Resource %s resid %d %s\n"%(resinfo[1], resinfo[0], resinfo[2]))
+	if resinfo:
+		fp.write("Resource %s resid %d %s\n"%(ascii(resinfo[1]), resinfo[0], ascii(resinfo[2])))
 	fp.write('"""\n')
 	fp.write('import aetools\n')
 	fp.write('Error = aetools.Error\n')
@@ -282,20 +284,49 @@
 		fp.write("import %s\n" % modname)
 	fp.write("\n\n_code_to_module = {\n")
 	for code, modname in suitelist:
-		fp.write("\t'%s' : %s,\n"%(code, modname))
+		fp.write("\t'%s' : %s,\n"%(ascii(code), modname))
 	fp.write("}\n\n")
 	fp.write("\n\n_code_to_fullname = {\n")
 	for code, modname in suitelist:
-		fp.write("\t'%s' : ('%s.%s', '%s'),\n"%(code, packagename, modname, modname))
+		fp.write("\t'%s' : ('%s.%s', '%s'),\n"%(ascii(code), packagename, modname, modname))
 	fp.write("}\n\n")
 	for code, modname in suitelist:
 		fp.write("from %s import *\n"%modname)
+	
+	# Generate property dicts and element dicts for all types declared in this module
+	fp.write("def getbaseclasses(v):\n")
+	fp.write("\tif hasattr(v, '_superclassnames'):\n")
+	fp.write("\t\tv._propdict = {}\n")
+	fp.write("\t\tv._elemdict = {}\n")
+	fp.write("\t\tfor superclass in v._superclassnames:\n")
+	fp.write("\t\t\tgetbaseclasses(superclass)\n")
+	fp.write("\t\t\tv._propdict.update(getattr(eval(superclass), '_privpropdict'))\n")
+	fp.write("\t\t\tv._elemdict.update(getattr(eval(superclass), '_privelemdict'))\n")
+	fp.write("\t\tv._propdict.update(v._privpropdict)\n")
+	fp.write("\t\tv._elemdict.update(v._privelemdict)\n")
+	fp.write("import StdSuites\n")
+	if allprecompinfo:
+		fp.write("\n#\n# Set property and element dictionaries now that all classes have been defined\n#\n")
+		for codenamemapper in allprecompinfo:
+			for k, v in codenamemapper.getall('class'):
+				fp.write("getbaseclasses(%s)\n" % v)
+
+	# Generate a code-to-name mapper for all of the types (classes) declared in this module
+	if allprecompinfo:
+		fp.write("\n#\n# Indices of types declared in this module\n#\n")
+		fp.write("_classdeclarations = {\n")
+		for codenamemapper in allprecompinfo:
+			for k, v in codenamemapper.getall('class'):
+				fp.write("\t%s : %s,\n" % (`ascii(k)`, v))
+		fp.write("}\n")
+
 	if suitelist:
 		fp.write("\n\nclass %s(%s_Events"%(packagename, suitelist[0][1]))
 		for code, modname in suitelist[1:]:
 			fp.write(",\n\t\t%s_Events"%modname)
 		fp.write(",\n\t\taetools.TalkTo):\n")
-		fp.write("\t_signature = %s\n\n"%`creatorsignature`)
+		fp.write("\t_signature = %s\n\n"%`ascii(creatorsignature)`)
+		fp.write("\t_moduleName = '%s'\n\n"%packagename)
 	fp.close()
 	
 def precompilesuite(suite, basepackage=None):
@@ -353,16 +384,16 @@
 	fp = open(fss.as_pathname(), 'w')
 	fss.SetCreatorType('Pyth', 'TEXT')
 	
-	fp.write('"""Suite %s: %s\n' % (name, desc))
+	fp.write('"""Suite %s: %s\n' % (ascii(name), ascii(desc)))
 	fp.write("Level %d, version %d\n\n" % (level, version))
-	fp.write("Generated from %s\n"%fname)
+	fp.write("Generated from %s\n"%ascii(fname))
 	fp.write("AETE/AEUT resource version %d/%d, language %d, script %d\n" % \
 		(major, minor, language, script))
 	fp.write('"""\n\n')
 	
 	fp.write('import aetools\n')
 	fp.write('import MacOS\n\n')
-	fp.write("_code = %s\n\n"% `code`)
+	fp.write("_code = %s\n\n"% `ascii(code)`)
 	if basepackage and basepackage._code_to_module.has_key(code):
 		# We are an extension of a baseclass (usually an application extending
 		# Standard_Suite or so). Import everything from our base module
@@ -421,7 +452,7 @@
 	if arguments:
 		fp.write("\t_argmap_%s = {\n"%funcname)
 		for a in arguments:
-			fp.write("\t\t%s : %s,\n"%(`identify(a[0])`, `a[1]`))
+			fp.write("\t\t%s : %s,\n"%(`identify(a[0])`, `ascii(a[1])`))
 		fp.write("\t}\n\n")
 		
 	#
@@ -443,7 +474,7 @@
 	# Generate doc string (important, since it may be the only
 	# available documentation, due to our name-remaping)
 	#
-	fp.write('\t\t"""%s: %s\n'%(name, desc))
+	fp.write('\t\t"""%s: %s\n'%(ascii(name), ascii(desc)))
 	if has_arg:
 		fp.write("\t\tRequired argument: %s\n"%getdatadoc(accepts))
 	elif opt_arg:
@@ -458,8 +489,8 @@
 	#
 	# Fiddle the args so everything ends up in 'arguments' dictionary
 	#
-	fp.write("\t\t_code = %s\n"% `code`)
-	fp.write("\t\t_subcode = %s\n\n"% `subcode`)
+	fp.write("\t\t_code = %s\n"% `ascii(code)`)
+	fp.write("\t\t_subcode = %s\n\n"% `ascii(subcode)`)
 	#
 	# Do keyword name substitution
 	#
@@ -487,7 +518,7 @@
 			ename = a[2][0]
 			if ename <> '****':
 				fp.write("\t\taetools.enumsubst(_arguments, %s, _Enum_%s)\n" %
-					(`kname`, identify(ename)))
+					(`ascii(kname)`, identify(ename)))
 				enumsneeded[ename] = 1
 	fp.write("\n")
 	#
@@ -665,7 +696,7 @@
 		else:
 			if self.fp:
 				self.fp.write('\nclass %s(aetools.ComponentItem):\n' % pname)
-				self.fp.write('\t"""%s - %s """\n' % (name, desc))
+				self.fp.write('\t"""%s - %s """\n' % (ascii(name), ascii(desc)))
 				self.fp.write('\twant = %s\n' % `code`)
 		self.namemappers[0].addnamecode('class', pname, code)
 		for prop in properties:
@@ -689,15 +720,15 @@
 		else:
 			if self.fp:
 				self.fp.write("class %s(aetools.NProperty):\n" % pname)
-				self.fp.write('\t"""%s - %s """\n' % (name, what[1]))
-				self.fp.write("\twhich = %s\n" % `code`)
-				self.fp.write("\twant = %s\n" % `what[0]`)
+				self.fp.write('\t"""%s - %s """\n' % (ascii(name), ascii(what[1])))
+				self.fp.write("\twhich = %s\n" % `ascii(code)`)
+				self.fp.write("\twant = %s\n" % `ascii(what[0])`)
 		self.namemappers[0].addnamecode('property', pname, code)
 	
 	def compileelement(self, elem):
 		[code, keyform] = elem
 		if self.fp:
-			self.fp.write("#        element %s as %s\n" % (`code`, keyform))
+			self.fp.write("#        element %s as %s\n" % (`ascii(code)`, ascii(keyform)))
 
 	def fillclasspropsandelems(self, cls):
 		[name, code, desc, properties, elements] = cls
@@ -708,12 +739,25 @@
 			return
 		plist = []
 		elist = []
+		superclasses = []
 		for prop in properties:
 			[pname, pcode, what] = prop
+			if pcode == "c@#^":
+				superclasses.append(what)
 			if pcode == 'c@#!':
 				continue
 			pname = identify(pname)
 			plist.append(pname)
+
+		superclassnames = []
+		for superclass in superclasses:
+			superId, superDesc, dummy = superclass
+			superclassname, fullyqualifiedname, module = self.findcodename("class", superId)
+			superclassnames.append(superclassname)
+
+		if self.fp:
+			self.fp.write("%s._superclassnames = %s\n"%(cname, `superclassnames`))
+
 		for elem in elements:
 			[ecode, keyform] = elem
 			if ecode == 'c@#!':
@@ -721,16 +765,16 @@
 			name, ename, module = self.findcodename('class', ecode)
 			if not name:
 				if self.fp:
-					self.fp.write("# XXXX %s element %s not found!!\n"%(cname, `ecode`))
+					self.fp.write("# XXXX %s element %s not found!!\n"%(cname, `ascii(ecode)`))
 			else:
 				elist.append((name, ename))
 		
 		if self.fp:
-			self.fp.write("%s._propdict = {\n"%cname)
+			self.fp.write("%s._privpropdict = {\n"%cname)
 			for n in plist:
 				self.fp.write("\t'%s' : %s,\n"%(n, n))
 			self.fp.write("}\n")
-			self.fp.write("%s._elemdict = {\n"%cname)
+			self.fp.write("%s._privelemdict = {\n"%cname)
 			for n, fulln in elist:
 				self.fp.write("\t'%s' : %s,\n"%(n, fulln))
 			self.fp.write("}\n")
@@ -741,7 +785,7 @@
 		self.namemappers[0].addnamecode('comparison', iname, code)
 		if self.fp:
 			self.fp.write("class %s(aetools.NComparison):\n" % iname)
-			self.fp.write('\t"""%s - %s """\n' % (name, comment))
+			self.fp.write('\t"""%s - %s """\n' % (ascii(name), ascii(comment)))
 		
 	def compileenumeration(self, enum):
 		[code, items] = enum
@@ -756,14 +800,14 @@
 	
 	def compileenumerator(self, item):
 		[name, code, desc] = item
-		self.fp.write("\t%s : %s,\t# %s\n" % (`identify(name)`, `code`, desc))
+		self.fp.write("\t%s : %s,\t# %s\n" % (`identify(name)`, `ascii(code)`, desc))
 		
 	def checkforenum(self, enum):
 		"""This enum code is used by an event. Make sure it's available"""
 		name, fullname, module = self.findcodename('enum', enum)
 		if not name:
 			if self.fp:
-				self.fp.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum), enum))
+				self.fp.write("_Enum_%s = None # XXXX enum %s not found!!\n"%(identify(enum), ascii(enum)))
 			return
 		if module:
 			if self.fp:
@@ -775,19 +819,19 @@
 		self.fp.write("\n#\n# Indices of types declared in this module\n#\n")
 		self.fp.write("_classdeclarations = {\n")
 		for k, v in self.namemappers[0].getall('class'):
-			self.fp.write("\t%s : %s,\n" % (`k`, v))
+			self.fp.write("\t%s : %s,\n" % (`ascii(k)`, v))
 		self.fp.write("}\n")
 		self.fp.write("\n_propdeclarations = {\n")
 		for k, v in self.namemappers[0].getall('property'):
-			self.fp.write("\t%s : %s,\n" % (`k`, v))
+			self.fp.write("\t%s : %s,\n" % (`ascii(k)`, v))
 		self.fp.write("}\n")
 		self.fp.write("\n_compdeclarations = {\n")
 		for k, v in self.namemappers[0].getall('comparison'):
-			self.fp.write("\t%s : %s,\n" % (`k`, v))
+			self.fp.write("\t%s : %s,\n" % (`ascii(k)`, v))
 		self.fp.write("}\n")
 		self.fp.write("\n_enumdeclarations = {\n")
 		for k, v in self.namemappers[0].getall('enum'):
-			self.fp.write("\t%s : %s,\n" % (`k`, v))
+			self.fp.write("\t%s : %s,\n" % (`ascii(k)`, v))
 		self.fp.write("}\n")
 
 def compiledata(data):
@@ -806,12 +850,12 @@
 def getdatadoc(data):
 	[type, descr, flags] = data
 	if descr:
-		return descr
+		return ascii(descr)
 	if type == '****':
 		return 'anything'
 	if type == 'obj ':
 		return 'an AE object reference'
-	return "undocumented, typecode %s"%`type`
+	return "undocumented, typecode %s"%`ascii(type)`
 
 dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
 def compiledataflags(flags):
@@ -824,6 +868,18 @@
 				bits.append(`i`)
 	return '[%s]' % string.join(bits)
 	
+def ascii(str):
+	"""Return a string with all non-ascii characters hex-encoded"""
+	if type(str) != type(''):
+		return map(ascii, str)
+	rv = ''
+	for c in str:
+		if c in ('\t', '\n', '\r') or ' ' <= c < chr(0x7f):
+			rv = rv + c
+		else:
+			rv = rv + '\\x%02.2x' % ord(c)
+	return rv
+	
 def identify(str):
 	"""Turn any string into an identifier:
 	- replace space by _
@@ -852,4 +908,4 @@
 if __name__ == '__main__':
 	main()
 	sys.exit(1)
-print identify('for')
\ No newline at end of file
+print identify('for')