Search code examples
objective-cmacoscocoaapplescriptcocoa-scripting

Issues with making Cocoa app AppleScript-scriptable


I want to make my app scriptable and am running into a few issues I hope some of you may help me with since my experience with making apps scriptable is not great.

The app is not document-based and has a several global named objects of the same class that I want to be accessed from AppleScript. For example, say that I want to have a several catalogues, each named, say "animals", "plants", "cars". I have managed to add several properties to the application class (in AppleScript), each representing one of the catalogues:

<class name="application" description="My application." code="capp" inherits="application">
  <cocoa class="NSApplication"/>
  <property name="animals catalogue" code="acat" description="…" type="catalogue" access="r">
    <cocoa key="animalsCatalogue"/>
  </property>
  <property name="cars catalogue" code="ccat" description="…" type="catalogue" access="r">
    <cocoa key="carsCatalogue"/>
  </property>
  <property name="plants catalogue" code="pcat" description="…" type="catalogue" access="r">
    <cocoa key="plantsCatalogue"/>
  </property>
</class>

I am able to successfully fetch any of those variables, e.g.

tell application "MyApp"
  set c to get animals catalogue
end tell

gets me really the catalogue variable («class ����» "Animals" of application "MyApp").

Unfortunately, where it gets tricky is when it comes to getting a property from the catalogue - for example 'empty'. Here is the catalogue definition:

<class name="catalogue" code="ctlg" description="...">
    <cocoa class="XYCatalogue"/>
    <property name="empty" code="empt" description="Is the catalogue empty?" type="boolean" access="r">
        <cocoa key="isEmpty"/>
    </property>
</class>

The issue here is that when running this AppleScript:

tell application "MyApp"
  set c to get animals catalogue
  get empty of c
end tell

results in an error: MyApp got an error: Can’t make «class ����» "Animals" into type specifier.

Implementation-wise, I have the NSApplication subclassed (and yes, I have specified the main class in Info.plist), which implements several methods, returning those particular catalogues. The XYCatalogue class implements the -objectSpecifier method this way:

return [[[NSNameSpecifier alloc] initWithContainerClassDescription:
                  [NSScriptClassDescription classDescriptionForClass:[NSApp class]]
                               containerSpecifier:nil 
                               key:@"allCatalogues" 
                               name:[self name]] autorelease];

The NSApplication subclass implements a -allCatalogues method that returns all the catalogues. I have tried even using the NSUniqueIDSpecifier and the NSPropertySpecifier, all in vain. And yes, the NSApplication subclass does implement both -valueWithName:inPropertyWithKey: and -valueInAllCataloguesWithName: methods and neither is invoked (have breakpoints in both of them).

I have sincerely read Apple's guide several times, however, still can't figure out where is the issue and I'm hanging on this for a few days now. I would be most thankful for any nudge in the right direction. Thanks!


Solution

  • The fact that you're not seeing the proper class name such as animals catalogue but instead see the «class» code is a good indicator that your -objectSpecifier function returns the wrong specifier.

    There is an important rule that one need to follow with -objectSpecifier:

    The key passed to -initWithContainerClassDescription: needs to be the same as the one that's entered for the property definition for that class under <cocoa key>. In your example, they do not match, however: The property accessor is called animalsCatalogue but the key you use in -objectSpecifier is allCatalogues. They need to match.

    Also, as the Apple docs say, the containerSpecifier may only be nil if the target is the application, but not for any other scriptable objects. The Cocoa Scripting engine won't probably report this back as an error if you pass nil for a non-application object, but the results will be similar to passing a wrong key.