more support for different kinds of objects

This commit is contained in:
Guido van Rossum 1995-02-05 16:59:27 +00:00
parent b19a645b69
commit 315592383e
2 changed files with 541 additions and 168 deletions

119
Mac/Lib/test/echo.py Normal file
View File

@ -0,0 +1,119 @@
"""'echo' -- an AppleEvent handler which handles all events the same.
It replies to each event by echoing the parameter back to the client.
This is a good way to find out how the Script Editor formats AppleEvents,
especially to figure out all the different forms an object specifier
can have (without having to rely on Apple's implementation).
"""
import AE
from AppleEvents import *
import Evt
from Events import *
import aetools
import sys
import MacOS
import traceback
kHighLevelEvent = 23 # Not defined anywhere for Python yet?
def main():
echo = EchoServer()
MacOS.EnableAppswitch(0) # Disable Python's own "event handling"
try:
echo.mainloop()
finally:
MacOS.EnableAppswitch(1) # Let Python have a go at events
echo.close()
class EchoServer:
suites = ['aevt', 'core']
def __init__(self):
self.active = 0
for suite in self.suites:
AE.AEInstallEventHandler(suite, typeWildCard, self.aehandler)
self.active = 1
def __del__(self):
self.close()
def close(self):
if self.active:
self.active = 0
for suite in self.suites:
AE.AERemoveEventHandler(suite, typeWildCard)
def mainloop(self, mask = everyEvent, timeout = 60*60):
while 1:
got, event = Evt.WaitNextEvent(mask, timeout)
if got:
self.lowlevelhandler(event)
def lowlevelhandler(self, event):
what, message, when, (h, v), modifiers = event
if what == kHighLevelEvent:
print "High Level Event:", `code(message)`, `code(h | (v<<16))`
try:
AE.AEProcessAppleEvent(event)
except AE.Error, msg:
print "AEProcessAppleEvent error:"
traceback.print_exc()
elif what == keyDown:
c = chr(message & charCodeMask)
if c == '.' and modifiers & cmdKey:
raise KeyboardInterrupt, "Command-period"
MacOS.HandleEvent(event)
elif what <> autoKey:
print "Event:", (eventname(what), message, when, (h, v), modifiers)
MacOS.HandleEvent(event)
def aehandler(self, request, reply):
print "Apple Event",
parameters, attributes = aetools.unpackevent(request)
print "class =", `attributes['evcl'].type`,
print "id =", `attributes['evid'].type`
print "Parameters:"
keys = parameters.keys()
keys.sort()
for key in keys:
print "%s: %.150s" % (`key`, `parameters[key]`)
print " :", str(parameters[key])
print "Attributes:"
keys = attributes.keys()
keys.sort()
for key in keys:
print "%s: %.150s" % (`key`, `attributes[key]`)
aetools.packevent(reply, parameters)
_eventnames = {
keyDown: 'keyDown',
autoKey: 'autoKey',
mouseDown: 'mouseDown',
mouseUp: 'mouseUp',
updateEvt: 'updateEvt',
diskEvt: 'diskEvt',
activateEvt: 'activateEvt',
osEvt: 'osEvt',
}
def eventname(what):
if _eventnames.has_key(what): return _eventnames[what]
else: return `what`
def code(x):
"Convert a long int to the 4-character code it really is"
s = ''
for i in range(4):
x, c = divmod(x, 256)
s = chr(c) + s
return s
if __name__ == '__main__':
main()
else: main()

View File

@ -1,39 +1,80 @@
"""Tools for use in AppleEvent clients and servers.
pack(x) converts a Python object to an AEDesc object
unpack(desc) does the reverse
packevent(event, parameters, attributes) sets params and attrs in an AEAppleEvent record
unpackevent(event) returns the parameters and attributes from an AEAppleEvent record
Plus... Lots of classes and routines that help representing AE objects,
ranges, conditionals, logicals, etc., so you can write, e.g.:
x = Character(1, Document("foobar"))
and pack(x) will create an AE object reference equivalent to AppleScript's
character 1 of document "foobar"
"""
import struct import struct
import types import string
from string import strip
from types import *
import AE import AE
import MacOS import MacOS
import macfs
import StringIO import StringIO
AEDescType = type(AE.AECreateDesc('TEXT', '')) AEDescType = type(AE.AECreateDesc('TEXT', ''))
def pack(x): FSSType = type(macfs.FSSpec(':'))
def pack(x, forcetype = None):
if forcetype:
if type(x) is StringType:
return AE.AECreateDesc(forcetype, x)
else:
return pack(x).AECoerceDesc(forcetype)
if x == None: if x == None:
return AE.AECreateDesc('null', '') return AE.AECreateDesc('null', '')
t = type(x) t = type(x)
if t == AEDescType: if t == AEDescType:
return x return x
if t == types.IntType: if t == FSSType:
vol, dir, filename = x.as_tuple()
fnlen = len(filename)
header = struct.pack('hlb', vol, dir, fnlen)
padding = '\0'*(63-fnlen)
return AE.AECreateDesc('fss ', header + filename + padding)
if t == IntType:
return AE.AECreateDesc('long', struct.pack('l', x)) return AE.AECreateDesc('long', struct.pack('l', x))
if t == types.FloatType: if t == FloatType:
# XXX Weird thing -- Think C's "double" is 10 bytes, but
# struct.pack('d') return 12 bytes (and struct.unpack requires
# them, too). The first 2 bytes seem to be repeated...
# Probably an alignment problem
return AE.AECreateDesc('exte', struct.pack('d', x)[2:]) return AE.AECreateDesc('exte', struct.pack('d', x)[2:])
if t == types.StringType: if t == StringType:
return AE.AECreateDesc('TEXT', x) return AE.AECreateDesc('TEXT', x)
if t == types.ListType: if t == ListType:
list = AE.AECreateList('', 0) list = AE.AECreateList('', 0)
for item in x: for item in x:
list.AEPutDesc(0, pack(item)) list.AEPutDesc(0, pack(item))
return list return list
if t == types.TupleType: if t == DictionaryType:
t, d = x
return AE.AECreateDesc(t, d)
if t == types.DictionaryType:
record = AE.AECreateList('', 1) record = AE.AECreateList('', 1)
for key, value in x.items(): for key, value in x.items():
record.AEPutKeyDesc(key, pack(value)) record.AEPutKeyDesc(key, pack(value))
if t == types.InstanceType and hasattr(x, '__aepack__'): return record
if t == InstanceType and hasattr(x, '__aepack__'):
return x.__aepack__() return x.__aepack__()
return AE.AECreateDesc('TEXT', repr(x)) # Copout return AE.AECreateDesc('TEXT', repr(x)) # Copout
def unpack(desc): def unpack(desc):
t = desc.type t = desc.type
if t == 'TEXT': if t == 'TEXT':
@ -42,6 +83,10 @@ def unpack(desc):
return 0 return 0
if t == 'true': if t == 'true':
return 1 return 1
if t == 'enum':
return mkenum(desc.data)
if t == 'type':
return mktype(desc.data)
if t == 'long': if t == 'long':
return struct.unpack('l', desc.data)[0] return struct.unpack('l', desc.data)[0]
if t == 'shor': if t == 'shor':
@ -50,11 +95,10 @@ def unpack(desc):
return struct.unpack('f', desc.data)[0] return struct.unpack('f', desc.data)[0]
if t == 'exte': if t == 'exte':
data = desc.data data = desc.data
# XXX See corresponding note for pack()
return struct.unpack('d', data[:2] + data)[0] return struct.unpack('d', data[:2] + data)[0]
if t in ('doub', 'comp', 'magn'): if t in ('doub', 'comp', 'magn'):
return unpack(desc.AECoerceDesc('exte')) return unpack(desc.AECoerceDesc('exte'))
if t == 'enum':
return ('enum', desc.data)
if t == 'null': if t == 'null':
return None return None
if t == 'list': if t == 'list':
@ -70,179 +114,377 @@ def unpack(desc):
d[keyword] = unpack(item) d[keyword] = unpack(item)
return d return d
if t == 'obj ': if t == 'obj ':
return unpackobject(desc.data) record = desc.AECoerceDesc('reco')
return desc.type, desc.data # Copout return mkobject(unpack(record))
if t == 'rang':
record = desc.AECoerceDesc('reco')
return mkrange(unpack(record))
if t == 'cmpd':
record = desc.AECoerceDesc('reco')
return mkcomparison(unpack(record))
if t == 'logi':
record = desc.AECoerceDesc('reco')
return mklogical(unpack(record))
if t == 'targ':
return mktargetid(desc.data)
if t == 'alis':
# XXX Can't handle alias records yet, so coerce to FS spec...
return unpack(desc.AECoerceDesc('fss '))
if t == 'fss ':
return mkfss(desc.data)
return mkunknown(desc.type, desc.data)
class Object:
def __init__(self, dict = {}):
self.dict = dict
for key, value in dict.items():
self.dict[key] = value
def __repr__(self):
return "Object(%s)" % `self.dict`
def __str__(self):
want = self.dict['want']
form = self.dict['form']
seld = self.dict['seld']
s = "%s %s %s" % (nicewant(want), niceform(form), niceseld(seld))
fr = self.dict['from']
if fr:
s = s + " of " + str(fr)
return s
def __aepack__(self):
f = StringIO.StringIO()
putlong(f, len(self.dict))
putlong(f, 0)
for key, value in self.dict.items():
putcode(f, key)
desc = pack(value)
putcode(f, desc.type)
data = desc.data
putlong(f, len(data))
f.write(data)
return AE.AECreateDesc('obj ', f.getvalue())
def nicewant(want): def mkfss(data):
if type(want) == types.TupleType and len(want) == 2: vol, dir, fnlen = struct.unpack('hlb', data[:7])
return reallynicewant(want) filename = data[7:7+fnlen]
return macfs.FSSpec((vol, dir, filename))
def mktargetid(data):
sessionID = getlong(data[:4])
name = mkppcportrec(data[4:4+72])
print len(name), `name`
location = mklocationnamerec(data[76:76+36])
rcvrName = mkppcportrec(data[112:112+72])
return sessionID, name, location, rcvrName
def mkppcportrec(rec):
namescript = getword(rec[:2])
name = getpstr(rec[2:2+33])
portkind = getword(rec[36:38])
if portkind == 1:
ctor = rec[38:42]
type = rec[42:46]
identity = (ctor, type)
else: else:
return `want` identity = getpstr(rec[38:38+33])
return namescript, name, portkind, identity
def reallynicewant((t, w)): def mklocationnamerec(rec):
if t != 'type': return `t, w` kind = getword(rec[:2])
# These should be taken from the "elements" of the 'aete' resource stuff = rec[2:]
if w == 'cins': return 'insertion point' if kind == 0: stuff = None
if w == 'cha ': return 'character' if kind == 2: stuff = getpstr(stuff)
if w == 'word': return 'word' return kind, stuff
if w == 'para': return 'paragraph'
if w == 'ccel': return 'cell'
if w == 'ccol': return 'column'
if w == 'crow': return 'row'
if w == 'crng': return 'range'
if w == 'wind': return 'window'
if w == 'docu': return 'document'
return `w`
def niceform(form): def getpstr(s):
if type(form) == types.TupleType and len(form) == 2: return s[1:1+ord(s[0])]
return reallyniceform(form)
else:
return `form`
def reallyniceform((t, f)): def getlong(s):
if t <> 'enum': return `t, f`
if f == 'indx': return ''
if f == 'name': return ''
if f == 'rele': return ''
return `f`
def niceseld(seld):
if type(seld) == types.TupleType and len(seld) == 2:
return reallyniceseld(seld)
else:
return `seld`
def reallyniceseld((t, s)):
if t == 'long': return `s`
if t == 'TEXT': return `s`
if t == 'enum':
if s == 'next': return 'after'
if s == 'prev': return 'before'
return `t, s`
def unpackobject(data):
f = StringIO.StringIO(data)
nkey = getlong(f)
dumm = getlong(f)
dict = {}
for i in range(nkey):
keyw = getcode(f)
type = getcode(f)
size = getlong(f)
if size:
data = f.read(size)
else:
data = ''
desc = AE.AECreateDesc(type, data)
dict[keyw] = unpack(desc)
return Object(dict)
# --- get various data types from a "file"
def getword(f, *args):
getalgn(f)
s = f.read(2)
if len(s) < 2:
raise EOFError, 'in getword' + str(args)
return (ord(s[0])<<8) | ord(s[1])
def getlong(f, *args):
getalgn(f)
s = f.read(4)
if len(s) < 4:
raise EOFError, 'in getlong' + str(args)
return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])
def getcode(f, *args): def getword(s):
getalgn(f) return (ord(s[0])<<8) | (ord(s[1])<<0)
s = f.read(4)
if len(s) < 4:
raise EOFError, 'in getcode' + str(args)
return s
def getpstr(f, *args):
c = f.read(1)
if len(c) < 1:
raise EOFError, 'in getpstr[1]' + str(args)
nbytes = ord(c)
if nbytes == 0: return ''
s = f.read(nbytes)
if len(s) < nbytes:
raise EOFError, 'in getpstr[2]' + str(args)
return s
def getalgn(f):
if f.tell() & 1:
c = f.read(1)
##if c <> '\0':
## print 'align:', `c`
# ---- end get routines
# ---- put various data types to a "file" def mkunknown(type, data):
return Unknown(type, data)
def putlong(f, value): class Unknown:
putalgn(f)
f.write(chr((value>>24)&0xff)) def __init__(self, type, data):
f.write(chr((value>>16)&0xff)) self.type = type
f.write(chr((value>>8)&0xff)) self.data = data
f.write(chr(value&0xff))
def __repr__(self):
return "Unknown(%s, %s)" % (`self.type`, `self.data`)
def __aepack__(self):
return pack(self.data, self.type)
def putword(f, value):
putalgn(f)
f.write(chr((value>>8)&0xff))
f.write(chr(value&0xff))
def putcode(f, value): def IsSubclass(cls, base):
if type(value) != types.StringType or len(value) != 4: """Test whether CLASS1 is the same as or a subclass of CLASS2"""
raise TypeError, "ostype must be 4-char string" # Loop to optimize for single inheritance
putalgn(f) while 1:
f.write(value) if cls is base: return 1
if len(cls.__bases__) <> 1: break
cls = cls.__bases__[0]
# Recurse to cope with multiple inheritance
for c in cls.__bases__:
if IsSubclass(c, base): return 1
return 0
def putpstr(f, value): def IsInstance(x, cls):
if type(value) != types.StringType or len(value) > 255: """Test whether OBJECT is an instance of (a subclass of) CLASS"""
raise TypeError, "pstr must be string <= 255 chars" return type(x) is InstanceType and IsSubclass(x.__class__, cls)
f.write(chr(len(value)) + value)
def putalgn(f):
if f.tell() & 1:
f.write('\0')
# ---- end put routines def nice(s):
if type(s) is StringType: return repr(s)
else: return str(s)
def mkenum(enum):
if IsEnum(enum): return enum
return Enum(enum)
class Enum:
def __init__(self, enum):
self.enum = "%-4.4s" % str(enum)
def __repr__(self):
return "Enum(%s)" % `self.enum`
def __str__(self):
return strip(self.enum)
def __aepack__(self):
return pack(self.enum, 'enum')
def IsEnum(x):
return IsInstance(x, Enum)
def mktype(type):
if IsType(type): return type
return Type(type)
class Type:
def __init__(self, type):
self.type = "%-4.4s" % str(type)
def __repr__(self):
return "Type(%s)" % `self.type`
def __str__(self):
return strip(self.type)
def __aepack__(self):
return pack(self.type, 'type')
def IsType(x):
return IsInstance(x, Type)
def mkrange(dict):
return Range(dict['star'], dict['stop'])
class Range:
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __repr__(self):
return "Range(%s, %s)" % (`self.start`, `self.stop`)
def __str__(self):
return "%s thru %s" % (nice(self.start), nice(self.stop))
def __aepack__(self):
return pack({'star': self.start, 'stop': self.stop}, 'rang')
def IsRange(x):
return IsInstance(x, Range)
def mkcomparison(dict):
return Comparison(dict['obj1'], dict['relo'].enum, dict['obj2'])
class Comparison:
def __init__(self, obj1, relo, obj2):
self.obj1 = obj1
self.relo = "%-4.4s" % str(relo)
self.obj2 = obj2
def __repr__(self):
return "Comparison(%s, %s, %s)" % (`self.obj1`, `self.relo`, `self.obj2`)
def __str__(self):
return "%s %s %s" % (nice(self.obj1), strip(self.relo), nice(self.obj2))
def __aepack__(self):
return pack({'obj1': self.obj1,
'relo': mkenum(self.relo),
'obj2': self.obj2},
'cmpd')
def IsComparison(x):
return IsInstance(x, Comparison)
def mklogical(dict):
return Logical(dict['logc'], dict['term'])
class Logical:
def __init__(self, logc, term):
self.logc = "%-4.4s" % str(logc)
self.term = term
def __repr__(self):
return "Logical(%s, %s)" % (`self.logc`, `self.term`)
def __str__(self):
if type(self.term) == ListType and len(self.term) == 2:
return "%s %s %s" % (nice(self.term[0]),
strip(self.logc),
nice(self.term[1]))
else:
return "%s(%s)" % (strip(self.logc), nice(self.term))
def __aepack__(self):
return pack({'logc': mkenum(self.logc), 'term': self.term}, 'logi')
def IsLogical(x):
return IsInstance(x, Logical)
class ObjectSpecifier:
"""A class for constructing and manipulation AE object specifiers in python.
An object specifier is actually a record with four fields:
key type description
--- ---- -----------
'want' type what kind of thing we want,
e.g. word, paragraph or property
'form' enum how we specify the thing(s) we want,
e.g. by index, by range, by name, or by property specifier
'seld' any which thing(s) we want,
e.g. its index, its name, or its property specifier
'from' object the object in which it is contained,
or null, meaning look for it in the application
Note that we don't call this class plain "Object", since that name
is likely to be used by the application.
"""
def __init__(self, want, form, seld, fr = None):
self.want = want
self.form = form
self.seld = seld
self.fr = fr
def __repr__(self):
s = "ObjectSpecifier(%s, %s, %s" % (`self.want`, `self.form`, `self.seld`)
if self.fr:
s = s + ", %s)" % `self.fr`
else:
s = s + ")"
return s
def __aepack__(self):
return pack({'want': mktype(self.want),
'form': mkenum(self.form),
'seld': self.seld,
'from': self.fr},
'obj ')
def IsObjectSpecifier(x):
return IsInstance(x, ObjectSpecifier)
class Property(ObjectSpecifier):
def __init__(self, which, fr = None):
ObjectSpecifier.__init__(self, 'prop', 'prop', mkenum(which), fr)
def __repr__(self):
if self.fr:
return "Property(%s, %s)" % (`self.seld.enum`, `self.fr`)
else:
return "Property(%s)" % `self.seld.enum`
def __str__(self):
if self.fr:
return "Property %s of %s" % (str(self.seld), str(self.fr))
else:
return "Property %s" % str(self.seld)
class SelectableItem(ObjectSpecifier):
def __init__(self, want, seld, fr = None):
t = type(seld)
if t == StringType:
form = 'name'
elif IsRange(seld):
form = 'rang'
elif IsComparison(seld) or IsLogical(seld):
form = 'test'
else:
form = 'indx'
ObjectSpecifier.__init__(self, want, form, seld, fr)
class ComponentItem(SelectableItem):
# Derived classes *must* set the *class attribute* 'want' to some constant
def __init__(self, which, fr = None):
SelectableItem.__init__(self, self.want, which, fr)
def __repr__(self):
if not self.fr:
return "%s(%s)" % (self.__class__.__name__, `self.seld`)
return "%s(%s, %s)" % (self.__class__.__name__, `self.seld`, `self.fr`)
def __str__(self):
seld = self.seld
if type(seld) == StringType:
ss = repr(seld)
elif IsRange(seld):
start, stop = seld.start, seld.stop
if type(start) == InstanceType == type(stop) and \
start.__class__ == self.__class__ == stop.__class__:
ss = str(start.seld) + " thru " + str(stop.seld)
else:
ss = str(seld)
else:
ss = str(seld)
s = "%s %s" % (self.__class__.__name__, ss)
if self.fr: s = s + " of %s" % str(self.fr)
return s
template = """
class %s(ComponentItem): want = '%s'
"""
exec template % ("Text", 'text')
exec template % ("Character", 'cha ')
exec template % ("Word", 'cwor')
exec template % ("Line", 'clin')
exec template % ("Paragraph", 'cpar')
exec template % ("Window", 'cwin')
exec template % ("Document", 'docu')
exec template % ("File", 'file')
exec template % ("InsertionPoint", 'cins')
def mkobject(dict):
want = dict['want'].type
form = dict['form'].enum
seld = dict['seld']
fr = dict['from']
if form in ('name', 'indx', 'rang', 'test'):
if want == 'text': return Text(seld, fr)
if want == 'cha ': return Character(seld, fr)
if want == 'cwor': return Word(seld, fr)
if want == 'clin': return Line(seld, fr)
if want == 'cpar': return Paragraph(seld, fr)
if want == 'cwin': return Window(seld, fr)
if want == 'docu': return Document(seld, fr)
if want == 'file': return File(seld, fr)
if want == 'cins': return InsertionPoint(seld, fr)
if want == 'prop' and form == 'prop' and IsType(seld):
return Property(seld.type, fr)
return ObjectSpecifier(want, form, seld, fr)
# Special code to unpack an AppleEvent (which is *not* a disguised record!)
aekeywords = [ aekeywords = [
'tran', 'tran',
'rtid', 'rtid',
@ -287,10 +529,22 @@ def packevent(ae, parameters = {}, attributes = {}):
for key, value in attributes.items(): for key, value in attributes.items():
ae.AEPutAttributeDesc(key, pack(value)) ae.AEPutAttributeDesc(key, pack(value))
# Test program
def test(): def test():
target = AE.AECreateDesc('sign', 'KAHL') target = AE.AECreateDesc('sign', 'KAHL')
ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0) ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0)
print unpackevent(ae) print unpackevent(ae)
raw_input(":")
ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0)
obj = Character(2, Word(1, Document(1)))
print obj
print repr(obj)
packevent(ae, {'----': obj})
params, attrs = unpackevent(ae)
print params['----']
raw_input(":")
if __name__ == '__main__': if __name__ == '__main__':
test() test()