The Application Kit: Scripting


The Application Kit: Scripting

Scripting is a way of using messages to control an application. The control is exercised through BHandler objects, since they're the objects that respond to messages.

Any message, especially any message delivered from a remote source, can be considered a scripting message, since it tells the application that receives it what to do. However, a "script" usually is thought of as a series of instructions (or in this case, messages) that put an application through its paces. Therefore, the scripting system must build on the bare messaging framework discussed in the previous section to make applications more vulnerable to outside control.

To aid scripting, the BeOS includes the following features:

The following sections explain how these features complement each other and together define a scripting system.


Messages, Properties, and Specifiers

To open objects to external control, the scripting system defines a small set of messages that target object properties. There are four such messages:

These messages are not system messages; they're dispatched by calling MessageReceived().

The messages are generic; none of them is specific to a particular property or BHandler. However, almost anything associated with an object can be conceived as a property of the object--its current state, another object it knows about, a service it provides, and so on. Therefore, these messages can potentially put an object under almost total control. For example, a simple on-screen gadget, like an on-off switch, could treat its label, size, location, current setting, enabled state, the window it's in, and what it turns on and off as properties. An object representing a modem might regard its data rate, current connection, readiness to send or receive, and even the data it transmits as properties.

Treating an attribute of an object as a property doesn't mean that the object must be implemented any differently; it means only that the object's message-handling code will recognize messages that name the property.

The messages listed above have meaning only to the extent that BHandlers provide code that responds to them for particular properties. It's up to each class derived from BHandler to decide which messages its objects will respond to and for which properties. For example, an on-off switch might honor both B_GET_PROPERTY and B_SET_PROPERTY messages for its current setting, but would probably not allow a B_SET_PROPERTY message to change the window it's in. A BHandler is as scriptable as you decide to make it.


Object Properties

A property is a value with a name. The name is a character string and is specific to an object's class. For example, a BaggyPants class might recognize properties such as "Waist", "Leg", "Zipped", and "Pocket". The data type of the property and the set of permissible values depend on the property. For example, the "Waist" property might take int32 measurements between 10 and 42 as values and "Zipped" could be either true or false.

In some cases, a property might be represented by another object. For example, "Pocket" could designate an object, possibly one with its own set of properties.

An object might also have more than one representative of a property. Although a BaggyPants object would have just one "Waist", it probably would have a pair of "Leg" values and likely more than one "Pocket". A property name can cover a set of data elements, provided the elements are all of the same type.

Therefore, a scripting message must not only name a particular property, it must also specify a particular instance of the property--which "Leg" or "Pocket" is targeted by a B_SET_PROPERTY message, for example. This is the task of a specifier that's added to each message.


Specifiers

A specifier has two jobs: It must name a property, and it must pick out a particular instance of the property. It therefore needs a structure that can combine various pieces of arbitrary information. Since this is exactly what BMessage objects are designed to do, a specifier takes the form of a BMessage inside another BMessage. The scripting BMessage holds its specifier BMessages in a named data array, just as it holds other data. However, because specifiers are a special breed with a peculiar role to play, they're added to the scripting message by a special function, AddSpecifier()--not by the functions that would normally be called to add one message to another. AddSpecifier() places the specifiers in a data field named "specifiers" and can often construct the specifier BMessage from information it's passed.

A specifier message has two required elements:

There are six standard specifier constants:

Applications can define other types of specifiers. The two required elements are a "property" field with the name of the property and a what constant that doesn't clash with those listed above (or with any that may be added in future releases). To prevent clashes, Be will never define specifier constants with values greater than B_SPECIFIERS_END. Define your own constants as increments from that value.


The Specifier Stack

A BMessage can contain more than one specifier. Suppose, for example, that a record is kept of what's in each "Pocket" of the BaggyPants object. (It doesn't matter whether the record is kept by the BaggyPants object or by a separate Pocket object.) If this is treated as a "Contents" property, a B_GET_PROPERTY message assigned to a BaggyPants object could ask for the first five items in the left hip pocket. It would need two specifiers: The one at index 0 would be for the "Contents" property and the one at index 1 would be for the "Pocket" property:

Specifier at index 0:
Property name: "Contents"
Specifier: B_RANGE_SPECIFIER
Specifier fields: "index" = 0, "range" = 5

Specifier at index 1:
Property name: "Pocket"
Specifier: B_NAME_SPECIFIER
Specifier fields: "name" = "left hip"

If we imagine a Wardrobe class with a "Pants" property, we can extend the example so that a message directed to a Wardrobe object can ask, "Get the first 5 items in the left hip pocket of the pair of pants that were worn yesterday." It would need an additional specifier something like this:

Specifier at index 2:
Property name: "Pants"
Specifier: B_REVERSE_INDEX_SPECIFIER
Specifier fields: "index" = 1

In more practical terms, a message sent to a BApplication object can use specifiers to target properties of a view. For example, it could ask, "Get the enclosing rectangle for the BView named "George" in the application's fifth window." The only limits on the number of specifiers are those imposed by the imagination and the application's architecture.

Clearly, the specifiers are ordered. The order of evaluation is the reverse of the order in which AddSpecifier() adds them to the "specifiers" array. The specifier with the highest index must be evaluated first. After each specifier is evaluated, it's popped from the stack so the one with the next highest index can be evaluated, and so on until none remain.

The specifier that must be evaluated next is the current specifier; GetCurrentSpecifier() opens that specifier in the BMessage and reveals its contents.


Resolving Specifiers

The presence of a specifier in a BMessage always raises a doubt about the designated handler for the message. That's because the specifier might specify another BHandler. Suppose, for example, that the BaggyPants and Pocket classes both derive from BHandler and that Pocket objects respond to REMOVE_HAND messages. A REMOVE_HAND message could be targeted to a BaggyPants object, but with a specifier naming one of its "Pocket" values. It would then be up to the BaggyPants object to make sure the message was redirected to the specified Pocket object. That object would respond to the message just like any other REMOVE_HAND message, without regard to the specifier.

To settle the question of which object should ultimately handle a message with specifiers, the message is passed to the target BHandler's ResolveSpecifier() hook function before being dispatched. Derived classes implement ResolveSpecifier() to examine the message and (at least) the current specifier to determine which BHandler should be the message target--or should be given a chance to resolve the next specifier. For example, the BaggyPants version of ResolveSpecifier() would resolve specifiers for its "Pocket" property by finding the specified Pocket object and making it the new target of the message.

ResolveSpecifier() is called once for each proposed target--as long as the message has unevaluated specifiers. For example, the Wardrobe object that gets the message illustrated in the previous section ("Get the first 5 items in the left hip pocket of the pants worn yesterday") would resolve the "pants worn yesterday" specifier and designate the BaggyPants object as the new target. The BaggyPants object would then resolve the "left hip pocket" specifier and name the appropriate Pocket object as the target. That object would recognize that it could respond to a "get the first 5 items" message and designate itself as the target, ending the series of ResolveSpecifier() calls.

Specifier Roles

From these examples, it's apparent that specifiers play at least two roles:

For both cases, BHandler classes must provide ResolveSpecifier() functions that evaluate the specifiers. For the first case, they must also implement a MessageReceived() function that responds to the message.

Unresolved Specifiers

If a specifier is malformed--for example, if it contains an out-of-range index--ResolveSpecifier() can prevent the message from being handled (simply by returning NULL). In that case, no further attempt is made to resolve specifiers and the message is not dispatched. (To explain what has happened, ResolveSpecifier() should send an error message in reply.)

If ResolveSpecifier() doesn't recognize the current specifier, it calls the inherited version of the function to give its base class a chance. If the specifier can't be resolved by any class, it should reach the BHandler version of ResolveSpecifier() (through successive calls to the inherited function). If the BHandler class also can't resolve the specifier, it presumes that the message can't be handled. It prevents the message from being dispatched and arranges for a B_MESSAGE_NOT_UNDERSTOOD reply.


Message Protocols

The specifier is just one part of the protocol for a scripting message. For example, a B_SET_PROPERTY message must contain a new value for the property being set and a B_GET_PROPERTY message anticipates a message in reply with the requested information.

Detailed protocols will be worked out as the scripting system is used and extended. For now, follow these limited guidelines:

The scripting system takes care of some replies. If a specifier isn't recognized, it will fall through to the BHandler version of ResolveSpecifier(), which sends a B_MESSAGE_NOT_UNDERSTOOD reply. If the specifiers can be resolved but the message isn't recognized, it falls through to the BHandler version of MessageReceived(), which also sends a B_MESSAGE_NOT_UNDERSTOOD reply.


Suites

To make scripting work, BHandler classes need to advertise--in documentation and at run time--the specifiers they can resolve and the messages they can respond to. If an object's scripting capabilities aren't documented, no one will know to use them. Moreover, unless it's possible to match those capabilities with an actual object at run time, the scripting enterprise will remain static and subject to guesswork.

To facilitate the exchange of information at run time, a class can group the specifiers and messages it understands into one or more sets--or suites--and assign each one a name. A suite name has the form of a MIME-like identifier with "suite" as the type and a specific name as the subtype. For example, "suite/vnd.Be-view" is the name of a set of scripting messages and specifiers that a BView object in the Interface Kit understands. Once a suite is named and documented, other classes may choose to support it with their own implementations.

At run time, you can query an object for the suites it supports by sending it a B_GET_SUPPORTED_SUITES message. In return, you can expect a B_REPLY message with an "error" field containing an error code. If the code is B_OK, the message should also have a field named "suites" with the names of all suites the target object understands.

To make this query work, scriptable BHandler classes implement a GetSupportedSuites() function that adds one or more suite names to the reply message. (Despite the fact that the message and the function have matching names, B_GET_SUPPORTED_SUITES is not a system message.)

A suite can include any type of message. It's not limited to messages with specifiers or those that target object properties. (However, all the suites currently defined in the BeOS are restricted to B_SET_PROPERTY and B_GET_PROPERTY messages. This will change in future releases.)


The Universal Suite

The implementations of ResolveSpecifier() and MessageReceived() in the BHandler class are inherited by all derived classes--as long as they call the inherited versions of these functions in their own implementations. Therefore, the BHandler class is able to impart a minimal, but important, set of scripting capabilities to every message-handling object, no matter what it's class. These capabilities constitute a universal suite shared by all objects that respond to messages. Because it's universal, the suite doesn't need a name.

However, it does require some explanation. Any BHandler can respond to:


Documentation

A suite name means nothing without some documentation detailing what specifiers and messages the suite includes and the protocols for using them. Since a suite can be implemented by more than one class, suites could reasonably be documented in a separate section apart from any particular class. However, for the current release, all suites the BeOS defines are supported by only one class. Also, some classes have scripting capabilities but don't give them suite names. Therefore, in this book, the class descriptions for scriptable BHandler objects have a "Scripting Support" section in the class overview with information about the scripting capabilities of the class. The BHandler and BApplication classes in this kit have "Scripting Support" descriptions. In the Interface Kit, the BWindow, BView, BTextView, BControl, and BTextControl classes have them.






The Be Book, in lovely HTML, for the BeOS Preview Release.

Copyright © 1997 Be, Inc. All rights reserved.

Be is a registered trademark; BeOS, BeBox, BeWare, GeekPort, the Be logo, and the BeOS logo are trademarks of Be, Inc.

Last modified September 18, 1997.