Haiku API Bindings
Scripting1
Not logged in

Documentation | Manual: Scripting1

Chapter 17: Using Scriptable Objects

In this chapter, we are going to learn how to use scriptable objects.

This program for this chapter is called Scripting.ext.

Scripting is a sort of "remote control" for other applications. It is implemented via the messaging system: you send a scriping Message and get a reply Message from the scriptable object. (Don't know what the messaging system is? Read up on the basics.)

The reply message will normally have B_REPLY as its what value, although an unsuccessful message may get a MESSAGE_NOT_UNDERSTOOD reply instead.

The reply message will always have an Int32 error field; if all went well, the value will be B_OK; otherwise it will be an error code.

Typically, if there is a value to return, it will be returned through the reply Message's result field; its type will depend on the scripting message.

Properties and Commands

Each Scriptable object has one or more named properties.

There are six scripting commands. Although it is possible to define your own, the Be Book recommends sticking to these standard commands.

The what value of the scripting Message should be one of the command constants.

In the list below, the value in parentheses is the code corresponding to the constant.

Specifiers

A scripting Message only contains a command (and in the case of B_SET_PROPERTY, a new value). In order to tell the target which property to apply the command to, you need to add a specifier to the message.

A specifier is simply another message, but instead of using Message.AddMessage, you must use Message.AddSpecifier to add it to the scripting Message.

The what value of the specifier should contain a specifier constant, and it should have a String field called property that contains the property name.

Although you can construct your own specifier Message and add it to the scripting Message, there are additional versions of Message.AddMessage that allow you to add the most common specifiers without constructing the specifier Message yourself.

B_DIRECT_SPECIFIER

The property name alone is used to specify the property.

Perl

$message->AddSpecifier(property => $property);

Python

message.AddSpecifier(property = property)

B_NAME_SPECIFIER

In addition to the name of the property, the String name of a particular instance of the property is added to the specifier message.

Perl

$message->AddSpecifier(
    property => $property,
    name     => $name,
);

Python

message.AddSpecifier(
    property = property,
    name     = name,
)

B_ID_SPECIFIER

In addition to the name of the property, the Int32 id of a particular instance of the property is added to the specifier message.

There is no AddSpecifier shortcut for this specifier.

B_INDEX_SPECIFIER

In addition to the name of the property, the Int32 index of a particular instance of the property is added to the specifier message. That is, this specifier will get the indexth instance of the property.

Perl

$message->AddSpecifier(
    property => $property,
    index    => $index,
);

Python

message.AddSpecifier(
    property = property,
    index    = index,
)

B_REVERSE_INDEX_SPECIFIER

Like B_INDEX_SPECIFIER, but gets the indexth entry counting from the end of the list.

There is no AddSpecifier shortcut for this specifier.

B_RANGE_SPECIFIER

Gets a number of instances of a property, specified by an index and a range.

Perl

$message->AddSpecifier(
    property => $property,
    index    => $index,
    range    => $range,
);

Python

message.AddSpecifier(
    property = property,
    index    = index,
    range    = range,
)

B_REVERSE_RANGE_SPECIFIER

Like B_RANGE_SPECIFIER, but starts at the indexth entry from the end of the list. Some objects count forward from this index, while others count backward.

There is no AddSpecifier shortcut for this specifier.

The specifier stack

Usually, you can only get a Messenger for an application, and not for any of the other objects that the application may contain. In order to send scripting Messages to other objects in an application, you need to add additional specifiers to the scripting Message.

Note

In some cases, it is possible to get Messengers for other objects via the scripting system; that is, the result field of a reply Message may contain a Messenger that targets one of these objects.

Specifiers are popped off the stack in the opposite order from which they were added. For example, the following code gets the frame (a Rect) of the second View of the Window named MainWin.

Perl

my $message = HaikuR1::Message->new(B_GET_PROPERTY);
$message->AddSpecifier(
    property => 'Frame'      # get the Frame
);
$message->AddSpecifier(
    property => 'View',      # of the second View
    index    => 1,           # (0-based index)
);
$message->AddSpecifier(
    property => 'Window',    # of the Window
    name     => 'MainWin',   # named 'MainWin'
);

Python

message = Message->new(B_GET_PROPERTY)
message.AddSpecifier(
    property = 'Frame'      # get the Frame
)
message.AddSpecifier(
    property = 'View',      # of the second View
    index    = 1,           # (0-based index)
)
message.AddSpecifier(
    property = 'Window',    # of the Window
    name     = 'MainWin',   # named 'MainWin'
)

Putting it all together

The Tracker has a scripting interface that allows you to (among other things) open a folder Window.

Perl

my $messenger = HaikuR1::Messenger->new("application/x-vnd.Be-TRAK");

my $message1 = HaikuR1::Message->new(B_EXECUTE_PROPERTY);
$message1->AddRef("data", "/boot/home");
$message1->AddSpecifier(property => "Folder");

my $reply1 = $messenger->SendMessage(
    message     => $message1,
    synchronous => 1,
);

$reply1->PrintToStream();

Python

messenger = Messenger("application/x-vnd.Be-TRAK")

message1 = Message(B_EXECUTE_PROPERTY)
message1.AddRef("data", "/boot/home")
message1.AddSpecifier(property = "Folder")

reply1 = messenger.SendMessage(
    message     = message1,
    synchronous = 1,
)

reply1.PrintToStream()

You can see from the printout of the reply that it actually contains a Messenger, which targets the new Window. But we're going to ignore that in order to demonstrate how to use the specifier stack.

Tracker Windows are named after the folder they display; so the Window that Tracker just opened will be named home. We can use that to get the Window, then find its first child View, then get that View's Frame.

Perl

my $message2 = HaikuR1::Message->new(B_GET_PROPERTY);
$message2->AddSpecifier(
    property => "Frame",
);
$message2->AddSpecifier(
    property => "View",
    index    => 0,
);
$message2->AddSpecifier(
    property => "Window",
    name     => "home",
);

my $reply2 = $messenger->SendMessage(
    message     => $message2,
    synchronous => 1,
);
my $frame = $reply2->FindRect("result");
$frame->PrintToStream();

Python

message2 = Message(B_GET_PROPERTY)
message2.AddSpecifier(
    property = "Frame",
)
message2.AddSpecifier(
    property = "View",
    index    = 0,
)
message2.AddSpecifier(
    property = "Window",
    name     = "home",
)

reply2 = messenger.SendMessage(
    message     = message2,
    synchronous = 1,
)
frame = reply2.FindRect("result")
frame.PrintToStream()

In addition to the standard Window scripting suite, Tracker Windows have their own suite, which allows you to get and count entries in that Window.

Perl

my $message3 = HaikuR1::Message->new(B_COUNT_PROPERTIES);
$message3->AddSpecifier(
    property => "Entry",
);
$message3->AddSpecifier(
    property => "Poses",
);
$message3->AddSpecifier(
    property => "Window",
    name     => "home",
);

my $reply3 = $messenger->SendMessage(
    message     => $message3,
    synchronous => 1,
);

$reply3->PrintToStream();
my $count = $reply3->FindInt32("result");

Python

message3 = Message(B_COUNT_PROPERTIES)
message3.AddSpecifier(
    property = "Entry",
)
message3.AddSpecifier(
    property = "Poses",
)
message3.AddSpecifier(
    property = "Window",
    name     = "home",
)

reply3 = messenger.SendMessage(
    message     = message3,
    synchronous = 1,
)

reply3.PrintToStream()
count = reply3.FindInt32("result")

Suites

Each scriptable object has a special property named Suites. The reply to this scripting message will contain one or more String fields under suites. Each class that supports scripting adds its own suite, then sends the Message on to the base class so it can add its suite.

Thus, the first suites string will represent the class of the target, while the second will represent the ancestor class, the third will represent that class's ancestor, and so forth, back to Handler.

In addition, the reply message will contain one flattened PropertyInfo object for each suite, under messages. Through this object, you can examine the suites supported by the target.