[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: AppleScript and MCL

First, AppleScript is a language. Programs written in this language
usually send AppleEvents to other applications. You can send AppleEvents
directly (without the high-level AppleScript language), and the file
"appleevent-toolkit" provides some functions for doing that. Ideally,
you'd like something that runs AppleScripts, so you could run something

(do_script "Tell application \"FileMaker Pro\" get the salary of the
table with type "employee" and id 999 in the database named
\"Personnel\" end tell")

(I have no idea what the AppleScript syntax for FileMaker Pro is, so
this script may not work).

I understand that the system provides support for doing this, but I
don't know the details or if anyone has done it from MCL.

Using AppleEvents directly is more efficient but also more difficult. If
you want to go anywhere with this, you'll need IM:Interapplication
Communications. The rest of this message should get you started.

To use AppleEvents, you typically specify an event (what you want to do)
and an object specifier (what you want to do it to).

In the above appleScript, the event would be of class "core" and id
"getd" (for getdata). Object specifiers talk about classes of objects,
which have properties and container classes. In the above AppleScript,
I've assumed that FileMaker Pro has a class called "table" which has
properties "type" (one type is "employee"), id and "salary." I've also
assumed that there's a class called "database" which can be used as a
container class for tables.

The first thing you need to do is to create an object specifier. There's
a CreateObjSpecifier trap, but it's not in MCL. The following code does
the equivalent:

;;;create-obj-specifier duplicates the functionality of CreateObjSpecifier
;;;DisposeInputs is always false
(defun create-obj-specifier (desired-class container keyform keydatadesc
  (declare (type aedesc container keydatadesc)
           (type desctype desired-class keyform))
  (with-aedescs (list-spec class-desc form-desc)
    (%stack-block ((classtype 4)
                   (formtype 4))
      (setf (%get-ostype classtype) desired-class)
      (setf (%get-ostype formtype) keyform)
      (ae-error (#_AECreateList (%null-ptr) 0 t list-spec))  ;create
objspec as a list
      (ae-error (#_AECreateDesc #$typeType classtype 4 class-desc))
      (ae-error (#_AECreateDesc #$typeEnumerated formtype 4 form-desc))
      (ae-error (#_AEPutParamDesc list-spec $keyAEDesiredClass class-desc))
      (ae-error (#_AEPutParamDesc list-spec $keyAEContainer container))
      (ae-error (#_AEPutParamDesc list-spec $keyAEKeyForm form-desc))
      (ae-error (#_AEPutParamDesc list-spec $keyAEKeyData keydatadesc)) 
      (ae-error (#_AECoerceDesc list-spec $typeObjectSpecifier objspec)))))

To make an object specifier of a certain type, you can use:

;;;make-spec makes an object specifier record of the given type, using either 
;;;the object's name or absolute position
;;;If both name and number are given, name is preferred
(defun make-spec (spec container obj-type name number)
  (let ((data-alloc-size (if name 256 4))
        (data-size (if name (length name) 4))
        (data-type (if name #$typeChar #$typeInteger))
        (data-form (if name $formName $formAbsolutePosition)))
    (with-aedescs (data-desc)
      (%stack-block ((ptr data-alloc-size))
        (if name
          (%put-cstring ptr name)
          (%put-long ptr number))
        (ae-error (#_AECreateDesc data-type ptr data-size data-desc))
        (create-obj-specifier obj-type container data-form data-desc spec))

You also need to specify a property of the object specifier, which you
can do with the following:

;;;make-property-spec makes a property object specifier record
;;;prop-spec should be an AEDesc, which will be returned with the
property obj specifier
;;;container should be an AEDesc for the container object (e.g. a window
;;;property should be a code for the property you want (e.g., $pName =
name property)
(defun make-property-spec (prop-spec container property)
  (with-aedescs (data)
    (%stack-block ((nameptr 4))
      (setf (%get-ostype nameptr) property)
      (ae-error (#_AECreateDesc #$typeProperty nameptr 4 data))
      (create-obj-specifier $cProperty container $formPropertyID data

The container for the top-level object (in this example, the database)
is the null specifier, which you can create with the following code:

;;;The null spec specifies the application object
(defun make-null-spec (spec)
  (%stack-block ((nulltype 4))
    (setf (%get-ostype nulltype) #$typeNull)
    (ae-error (#_AECreateDesc #$typeNull nulltype 4 spec))

The code to make the object specifier would be:

(with-aedescs (null-desc database test db-table salary-spec)
   (make-null-spec null-desc)
   (make-spec database null-desc $typeDatabase "Personnel" nil)
   (make-test-spec test '(and (type "Employee") (id 999)))
   (make-spec-form-test db-table database $typeDBRecord test)
   (make-property-spec salary-spec db-table $pSalary)

In all this code, variables starting with $ are 4-character keyword
constants (the ones starting with #$ are already defined). I haven't
written make-test-spec or make-spec-form-test. The latter is almost
identical to my make-spec; the former is a little work, but IM:IC, pages
6-15,17 give you the basic outline for it.

Anyway, this code produces the AEDesc salary-spec, which specifies:
"the salary of the table with type Employee and ID 999 in the database
with name Personnel"

Now, you send the appleevent:

(with-aedescs (appleevent reply target)
   (ccl::create-signature-target target :FMKR)
   (apply 'create-appleevent appleevent $kAECoreSuite :|getd| target nil)
   (#_AEPutParamDesc appleevent #$keyDirectObject salary-spec)
   (send-appleevent appleevent reply :reply-mode :wait-reply)

I've guessed that FileMaker Pro's signature is :FMKR. Send-appleevent
should return when FileMaker handles the appleevent. The last thing to
do is to get the information out of the reply. Assuming salary is
returned as a longint:

(ccl::ae-get-parameter-longinteger reply #$keyDirectObject t)

should return the salary.

Simple, huh?