« | Home | »

Cocoa Scripting: Properties with Multiple Data Types

The SDEF (Scripting Definition) XML dictionary format allows you to define properties that have multiple data types. This allows applications to support the as parameter to the AppleScript Get command. However, Cocoa Scripting does not fully support the notion of properties with multiple data types. This article describes how to implement multiple data type support using Cocoa Scripting.

Why Do This?

Before I get into the code, let me briefly describe why you might want to do this in your application. Simply put, the value of certain properties may be expressed in difference data types. For example, you might have a selection property. Lets say that by default, the selection property returns the selected text in your application. However, you may also want to allow the user to ask for a reference to the location of the selected text:

get selection --> "Hello World"
get selection as string --> "Hello World"
get selection as reference --> word 2 thru 3 of document 1

When the scripter tries to alter the selection, they may do it in two ways:

set selection to "Goodbye" --> Replace the selected text with "Goodbye"
set selection to word 1 of document 1 --> select the first word

CAUTION: please use multiple data types correctly. The value returned for each data type should represent the same value, but in different ways. Correct use of multiple types avoids the need to have multiple versions of a given property in your dictionary, each returning the same value expressed in a different way.

The SDEF

Here’s a snippet from an SDEF defining a selection property that supports two data types: text and specifier.

  
    
    
    
  

Supporting Set Operations (Easy)

Cocoa Scripting facilitates Set operations for properties with multiple types. It will convert the incoming AppleEvent data to an instance of NSString for a text value or an instance of NSScriptObjectSpecifier if the value is an object reference (specifier). Here’s how you might implement the setAESelection: accessor:

- (void) setAESelection:(id) value
{
  if ([value isKindOfClass:[NSString class]])
  {
    // replace the selected text with the incoming value
  }
  else if ([value isKindOfClass:[NSScriptObjectSpecifier class]])
  {
    // change the selection to the range of text specified in the
    // incoming object specifier
  }
}

Supporting Get-As Operations (A Little Harder)

Here is where Cocoa Scripting leaves you to your own devices. The problem is that the desired data type is not passed to the accessor function. You have to get this from the current AppleEvent yourself. I use this function:

DescType FSRequestedTypeForCurrentEvent()
{
  NSAppleEventDescriptor* event = [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent];
  NSAppleEventDescriptor* requestedType = [event descriptorForKeyword:keyAERequestedType];

  if (requestedType)
    return [requestedType typeCodeValue];
  else
    return typeBest;
}

Then, the Accessor can be written like this:

- (id) AESelection
{
    switch (FSRequestedTypeForCurrentEvent())
    {
    case typeBest:  // the default if no 'as' parameter specified
    case typeText:
    case typeUnicodeText:
        // return an NSString instance containing the selected string
        break;

    case typeObjectSpecifier:
        // return an NSScriptObjectSpecifier instance describing the
        // location of the selection
        break;

    default: // unsupported type
        [[NSScriptCommand currentCommand] setScriptErrorNumber: errAEWrongDataType];
        return nil;
    }
}

NOTE: I generally have AppleEvent specific accessors for use with Cocoa Scripting that in turn use my bindings compatible accessors. This is because Cocoa Scripting needs to support things like multiple data types, and reports errors in a different way.


About this entry