Interactive Object Architecture 1.0b14

UserLand Software, Inc.

© copyright 1993, UserLand Software, Inc.

UserLand Software is the developer of the UserLand Frontier scripting system. The company is located at 400 Seaport Court, Redwood City, CA 94063. 415-369-6600, 415-369-6618 (fax). UserLand, Frontier, Frontier Runtime and Frontier Extras are trademarks of UserLand Software, Inc. Other product names may be trademarks or registered trademarks of their owners.

Email: userland.dts@applelink.apple.com. If youÕre an AppleLink user, check out the UserLand Discussion Board under the Third Parties icon. CompuServe users enter GO USERLAND at any ! prompt.

Comments, questions and suggestions are welcome!

Confidentiality

This specification and the IOA Toolkit is UserLand Confidential.

It is being disclosed to a very small group of people, assembled in Section/Library 15 at GO USERLAND on CompuServe.

IOA may at some time be an open specification, but it is not open today. We want your feedback, but you may not share this information with anyone, and may not discuss it outside of Section 15 on CompuServe.

Interface design tools is a competitive market. IOA is a competitive advantage. Any breach of confidentiality could seriously hurt UserLand Software. WeÕre trusting you.

Keep it confidential.

Dave Winer

UserLand Software


Introduction

IOA is an acronym for Interactive Object Architecture. ItÕs a specification and a Toolkit. The purpose of IOA is to open up Card Editor/Iowa Runtime so that UserLand and others can add new object types.

IOA objects build on AppleÕs Component Manager. You must be running either System 7.1 or QuickTime 1.0 or greater to build or run IOA components. All source examples are provided in THINK C, as is the IOA Toolkit.

Writing IOA components is not for script writers. You must be a system-level C or Pascal programmer with good debugging skills. Writing IOA components is very similar to writing a custom menu definition handler or control definition. But the IOA protocol is more comprehensive and powerful, so thereÕs more to understand.

To install: copy the folder containing the IOA Toolkit into your Frontier SDK folder, which should be a sub-folder in the folder that contains your THINK C application.

Start by looking at one of the sample objects provided with the IOA Toolkit. Each object implements a set of callback routines that perform operations like drawing the object, handling a mouse click or a keystroke, or recalculating the value of an object. Details are provided in the IOA Callbacks section, below.

Some callbacks are optional. In some cases, the default behavior is what you want. The IOA Toolkit automatically fills in defaults for you. Look at ioa.c to see what the default callbacks do. See the Config Records section, below for more info.

Each object also sets a group of boolean flags that describe the objectÕs behavior in relationship to other objects, and in relationship to the framework.

All callbacks receive a handle to a record that stores all information about the object, a hdlobject. The object record contains handles to strings containing the objectÕs value, name, and script. The objectrect field tells you where to draw the object. There are many more fields in each object record. See ÒObject RecordsÓ for details.


Contents of the resource fork of an IOA component

Open up one of the IOA files with ResEdit or Resorcerer. HereÕs a list of the resources that are contained in a typical file:

¥  CODE 63

Contains the code of the IOA component. ThereÕs no absolute requirement that this be resource 63, but if you use a different resource number, be sure you update the thng resource accordingly.

¥  ICN# 128

Not sure what the Component Manager does with this. But itÕs a requirement that each component have an icon.

¥  SICN 130

The icon for the object type. It appears in the tool palette in Card Editor. It must be in resource 130.

¥  STR 128

The short description for this object type. Displayed in the Things! Control Panel, supplied by Apple.

¥  STR 129

The long description for this object type. Displayed in the Things! Control Panel, supplied by Apple.

¥  thng 128

Ties all the resources in this file together. See the Component Manager docs for details.

¥  vers 1

Normal version resource.

¥  vers 2

Normal version resource.

You can add other resources to your file, and access them using OpenComponentResFile and CloseComponentResFile. See ioa.c for an example of their use.

The file type must be thng. The creator can be anything you like. All UserLand-supplied objects have a creator of LAND.


Object Records

All the information about each object is stored in an object record.

The record contains handles to the objectÕs name, value, script and error message. It tells you where to draw the object.

HereÕs the declaration for object records from ioa.h:

typedef struct tyobject {      

struct tyobject **nextobject; /*next object in the object list*/      
struct tyobject **nextselectedobject; /*part of the selection list structure*/ 
struct tyobject **childobjectlist; /*for group objects*/
struct tyobject **nextinthread; /*flat list of objects, for run mode*/
struct tycard **owningcard; /*each object points back to the card it appears in*/
tyobjecttype objecttype; /*a button, checkbox, static text, etc.*/    
Rect objectrect; /*the display rectangle for the object*/     
Handle objectname; /*the variable name for this object*/
Handle objectvalue; /*the string that's displayed in the object*/     
Handle objectscript; /*the script that runs when the object is hit*/  
Handle objecterrormessage; /*error generated when the object was calculated*/
Handle objectdata; /*a refcon handle*/   
boolean objectflag: 1; /*for buttons, true --> bold, checkboxes, the box is checked*/
boolean objectvisible: 1; /*if false, the object isn't drawn*/
boolean objectenabled: 1; /*if false the object is disabled*/
boolean objecttransparent: 1; /*if true, don't erase the rect before drawing object*/
boolean objectautosize: 1; /*enables auto-resizing*/   
boolean objectinval: 1; /*indicates the object needs re-display*/     
boolean objecthasframe: 1; /*do we draw a frame for the object?*/     
boolean objecttmpbit: 1; /*for any temporary use at all*/     
boolean objectselected: 1; /*if true, the object is part of the selection list*/
OSType objectlanguage; /*component subtype for linked script's interpreter*/
Handle objecteditrecord;  
short objectfont;  
short objectfontsize;     
short objectstyle; 
tyjustification objectjustification;     
RGBColor objectfillcolor; 
RGBColor objecttextcolor; 
RGBColor objectframecolor;
short objectdropshadowdepth;      
short objectlinespacing;  
short objectindentation;  
short sorttag; /*used for sorting the object list by location*/
} tyobject, *ptrobject, **hdlobject;

Most objects need to access or set only a few of the fields of the object record. Here are some comments on each of the fields, and when youÕre likely to use them.

nextobject

Often, thereÕs no need to access this field directly, since IOAvisitobjects is provided to traverse an object list. Radio button objects use this field to traverse the objectÕs list, turning off all the other radio button types.

nextselectedobject, childobjectlist, nextinthread

These are the ÒnextÓ pointers for three list structures: the selection list, a group objects list (nil if the objecttype ­ grouptype) and the flat thread linking all the objects together.
Few if any IOA components will need to access these handles.

owningcard

This field is provided in case your object needs to get information about the card that contains the object. See the declaration of tycard in ioa.h for details.

objecttype

You know that objecttype is your object type. If youÕre visiting other objects in the object list, you will want to check the objecttype field to determine the type of each object youÕre looking at. ItÕs cool to make changes to your own objects, but tread very carefully in changing the attributes of other peopleÕs objects.

objectrect

A very important field. It determines where you do your drawing and editing. In most cases, objects only draw inside their object rectangle. But certain types (e.g. bold buttons) draw outside of their rectangle. This is a historic (ie deeply ingrained) feature, itÕs not likely to change.

objectname

This is used by the framework to resolve references to other objectÕs values in scripts.

objectvalue

Usually this is the text you display when drawing your object. Some objects  display a function of the object value. Icons for example, convert the value to a number, and display the icon family resource with that number.

objectdata

This is entirely up to you. ItÕs a handle, with no type. When the card is saved, the handle is packed and written to the file. If you have any handles linked into this record, they will not be saved, but the address of the handle will be saved. This is always the wrong value. Nil out linked handles in your postUnpack callback.

objectflag

This boolean is set from the first command in the Object menu in Card Editor. It can be used to toggle your object, e.g. checked/unchecked checkboxes. Or bold/not bold buttons. ItÕs up to you to determine how the flag is interpreted, or not interpreted.
You can change whatÕs displayed in the Object menu when your object is selected by setting the objectFlagName field of the objectConfig record. ItÕs described below.

objectvisible, objectenabled

If the objectvisible boolean is false, the framework wonÕt call your draw routine. It presets the text color to gray if objectenabled is false, before calling your draw routine.

objecttransparent

Normally, youÕll erase the rectangle containing the object before drawing it. If objecttransparent is true, you should not erase the rectangle.

objectinval

Set this true at any time to force a redraw of the object the next time thru the main event loop. If your object changes the state of any other object, it should set its inval boolean true.

objectautosize

Set this true in your init callback, or before editing, to activate IowaÕs automatic resizing feature on this object.

objecttmpbit

Can be used for whatever purpose you like. But donÕt depend on its value remaining constant after you return from your callback.

objectselected

This boolean is true if the object is in the selection list. It can only be true in Card Editor.

objectlanguage

Is the 4-character identifier of the scripting component that runs the script in objectscript.

objecteditrecord

An edit buffer thatÕs non-nil only when the objectÕs text is being edited. Not sure what an object can do with this handle. It is used extensively in the framework.

objecthasframe, objectfont, objectfontsize, objectstyle, objectjustification, objectfillcolor, objecttextcolor, objectframecolor, objectdropshadowdepth

These attributes are dealt with automatically by the framework. You should not change their values, and when drawing your object, you donÕt need to set up the Mac toolbox to use these values -- itÕs already been done for you by the framework.

objectlinespacing, objectindentation

These attributes are waiting for the day when we have a better text editor wired into editable text objects and static text objects.

sorttag

A temporary field used when sorting lists. You can use it like the tmpbit if you absolutely have to.

Config Records

Iowa needs certain basic info in order to manage your object. One of the calls you must implement is the getConfigRecord callback. It returns an objectConfig record, which contains the following fields:

char objectTypeName [32];

A string of at most 31 characters containing the typeÕs name. ItÕs used in displaying information about the object in the 2click dialog in Card Editor.

long objectTypeID;

The type number assigned to this object.
Object yypes are 32-bit signed numbers. To avoid the need for an object ÒreunionÓ utility, IOA developers should ask UserLand for a unique object type, or range of object types. ThereÕs no requirement that you tell us what your object does. All types less than 128 are reserved.
1/19/93: HereÕs a list of current IOA object types implemented by UserLand Software:
grouptype = 1 /*implemented internally in framework*/
checkboxtype = 2
radiobuttontype = 3
picturetype = 4
statictexttype = 5
edittexttype = 6
buttontype = 7 /*note two missing types, following*/
recttype = 10
linetype = 11
ovaltype = 12
icontype = 13
formulatype = 14
popuptype = 15
clonetype = 16 /*implemented internally in framework*/

char objectFlagName [32];

Different objects use the objectflag field of the object record in different ways. Checkboxes and radio buttons are on if their flag is true, off if the flag is false. Buttons are bold if the flag is true.
If you donÕt make use of the flag field, leave this string empty. Otherwise set it to explain your interpretation of the objectflag. When only objects of your type are selected, the first item in the Object menu will reflect your interpretation of the objectflag field. In all other cases it will display as ÒFlag.Ó

Handle hsmallicon;

ioa.c automatically fills this field in by reading SICN 130 from the componentÕs resource fork.

boolean frameWhenEditing;

If true, the framework draws a frame around the object when its value is being edited.

boolean canEditValue;

If true, when the user presses the Enter key with the object selected, the value of the object is edited. For example, static text objects definitely want to allow the value to be edited. Icon objects donÕt.

boolean toggleFlagWhenHit;

If true, when the object is clicked in runmode, we invert its objectflag value, and redraw the object.
Checkboxes and radiobuttons both have this bit turned on.

boolean mutuallyExclusive;

If true, when Card Runner starts up the card:
¥  All objects of this type are turned off, their objectflag is set to false.
¥  The ÒfirstÓ mutually exclusive object in each group is turned on. Ordering is visual. The topmost, leftmost object is the first object in a group.
¥  As usual, the top-level of the card is considered to be a group.

boolean speaksForGroup;

When Iowa is determining the value of a named group object, it scans the group looking for objects that are willing to speak on behalf of the group. When determining the value of a group, the value of the group is the value of the first speaker whose objectflag is on.
Radio button objects are speakers. The value of the first (and only) radio button in a group thatÕs turned on is the value of the group.
This flag makes it possible for other object types to behave like radio buttons.

boolean handlesMouseTrack;

Set this true if your object type needs to handle run-mode mouse tracking for itself. Popup menus and editable text objects handle their own mouse tracking.
If your object doesnÕt handle mouse tracking, Card Runner handles tracking for you. HereÕs how:
¥  It sets the tracking field of the card record to true.
¥  While the mouse is down, it calls your drawObject callback repeatedly. If the mouse is pointing in your object rectangle, the trackerpressed field of the card record is set true, otherwise itÕs false.
¥  A handle to the card record is linked into each object record, in the owningcard field.
In your drawObject callback you should watch for this and draw your object ÒhotÓ if the conditions are met:
hdlcard hc = (**h).owningcard;
if ((**hc).tracking && (**hc).trackerpressed)

draw it hot

else

draw it normally

See radio.c and checkbox.c for examples.

boolean editableInRunMode;

Set this to true if your object can be edited by the user when the card is running.
If so, your editObject callback will be called when the user clicks on the object, or when the user moves to your object with the tab key. editObject takes a boolean parameter indicating whether you should activate your editor or deactivate it.

boolean isFontAware;

Set this to true if you care about the font, size and style of your text. If you donÕt care, the Edit menuÕs styling sub-menus will be disabled when your object is selected. Pictures, icons, lines, rectangles and ovals set this false. Other object types set it true.
This flag controls the ÒglobalÓ styling of the object. For example, you can set the font of an entire text object to Geneva, but you cannot selectively style a single word in a text object.
If more than one object is selected, and one or more is font-aware, the menus are enabled, but a font command will only set the attributes of objects that are font-aware.
If you are not font-aware, you can depend on your objectÕs objectfont, objectfontsize, objectstyle and objectjustification remaining unchanged.

boolean alwaysIdle;

If this flag is set true, your idleObject callback will be called on every idle loop when the card is running, even if it isnÕt currently being edited.
ItÕs very rare for objects to set this bit, in fact, none of the UserLand-supplied objects (1/19/93) do. But itÕs forseeable that some object might want to get a shot at the processor whenever the machine is idle.

IOA Callbacks

HereÕs a description of each of the callback routines implemented by each IOA component:

void getConfigRecord (tyconfigrecord *);

This is handled automatically by the IOA Toolkit in ioa.c, itÕs called when the component receives its kComponentOpenSelect message. It sets defaults for callbacks and flags and then calls your setupconfig routine, which every IOA component must implement.

boolean initObject (tyobject *);

Fill in the fields of the object record with the defaults for your object type.
Return true if you want the object to be edited, false otherwise.
Before youÕre called, the object fields were set to default values for all new objects. The objecttype field was set to your type, and objectautosize was set true if the card designer clicked to create the new object, false if the objectÕs rectangle was determined by the user dragging to create the new object.
If your object needs to be recalculated after the object record is allocated, set the object recordÕs objecttmpbit field to true. The object will be recalcÕd before Iowa returns to its main event loop. (Popup menus use this feature).

void drawObject (hdlobject);

Draw the object using its objectrect and objectvalue and perhaps other information stored in the object record.
Normally, after you draw the object, if itÕs selected, the framework will draw a selection frame and a ÒnubÓ around the objectÕs rectangle. If your object looks bad with a frame around it, set the objectÕs objecttmpbit true, and no frame will be drawn. A nub is always drawn. [Frame object types set objecttmpbit true.]

boolean clickObject (hdlobject, hdlobject, Point, boolean);

The user clicked in your object while the card is running.
The first object youÕre passed is called listhead, itÕs the first object in the group containing the object, or if itÕs not contained in a group, the first object in the card. Some object types need to operate on other objects, for example radio buttons turn off all radio buttons except for the one that was clicked on.
The second hdlobject is the one that was clicked on.
The third parameter is the mouse location when the mouse was clicked.
The fourth parameter is true if the shift key is down, false if it is up.
Return true if you changed the value of any object, false otherwise.

boolean editObject (hdlobject, boolean);

Called when the user clicks on your object in run mode, or when the user moves into your object with tab key. In both these cases, the boolean parameter is true, indicating that you should start editing the object.
It is also called with the boolean set false when the user clicks on a different object to edit it, or tabs out of your object.
You can use the objecteditrecord field of your object record to store a record describing the editing state of your object. (Not sure about this. DW 1/23/93)

boolean idleObject (hdlobject)

Called in run mode when your object is the active editing object while waiting for the user to do something.
Editable text objects flash the insertion point.

boolean setObjectCursor (hdlobject, Point);

In run mode, the mouse cursor is pointing at your object. Set the cursor accordingly.
Return false if you want the default arrow cursor.
The second parameter is the current mouse location, in case your choice of cursor depends on where in the object the mouse is pointing.
Your setObjectCursor handler will only be called if the object is enabled.

boolean keystrokeObjectCallback (hdlobject, char);

Called in run mode when the user enters a keystroke when your object is the active editing object.
Editable text objects insert the character at the insertion location.

void cleanupObject (hdlobject, short, short, Rect *);

Change the rectangle so the object can exactly display its contents.
We pre-compute the height and width of the text value of the object, and pass them as the second and third parameters. Many objects determine their size as a function of the size of the text (e.g. checkboxes, buttons). But you can ignore these values if you want.
Return the object rectangle. DonÕt worry about its position, just its size. After you return, the framework takes care of aligning the corners of the rectangle to the grid.

boolean recalcObject (hdlobject, boolean);

If your objectÕs value isnÕt calculated, just return true.
The boolean parameter is true if this is a major recalc, false if itÕs a minor one. If it is, you have to decide whether it gets calculated only when the card starts up (a major recalc) or whenever the value of another object changes (a minor recalc).
Examples: popup menus are evaluated when the card starts up. Same with text objects, checkboxes and radio buttons. Formulas recalc on major and minor recalcs.

boolean canReplicateObject (hdlobject);

Called when the user hits the Return key with your object selected. Look at the fields of the object record to determine if the Return key should be used to create a new object.
Most objects simply return true without checking the fields. Exceptions are static text objects and editable text objects.

void getObjectInvalRect (hdlobject, Rect *);

Return the rectangle that you want invalidated to force a redraw of the object. Usually this is just (**h).objectrect, but in some cases (e.g. bold buttons) itÕs necessary to invalidate more than the objectÕs rectangle.

boolean getObjectEditRect (hdlobject, Rect *);

Return false if the object canÕt be edited.
Otherwise return the editing rectangle for the object. ItÕs usually derived from the objectrect field of the hdlobject.

boolean getValueForScript (hdlobject, Handle *);

Return a handle containing the value of the object, in text form, to be substituted in a script.
Although your code shouldnÕt care, getValueForScript is only called when your object has a name and itÕs referenced by the script linked into another object.
For example, a radio button object returns true if itÕs turned on, false if not. An editable text object returns the current text of the object, in double quotes.
The syntax of your returned value should match conventions across scripting languages. The constants true and false are recognized by both Frontier and AppleScript. Quoted strings use the same syntax in both systems. In Frontier 2.1, list constants will be supported, so itÕs safe to return one, for example: {ÒRedÓ, ÒGreenÓ, ÒBlueÓ}.
Doug says that this should return an Apple Event descriptor instead. What do you think?

boolean postUnpackObject (hdlobject);

After the framework unpacks an object from a packed card, you have a shot at the object record.
For example, popup objects nil out a handle that gets saved in its data record and intializes other fields.
The returned boolean value is ignored.

boolean debugObject (hdlobject, Str255);

Return true if the fields of the object record are consistent. If they are not consistent, return false, and set the string to your error message. This is used for debugging the Iowa framework.
Current implementation of all UserLand-supplied objects simply returns true.

boolean catchReturn (hdlobject);

The user just hit the Return key in run mode. Does your object want to catch it? Bold buttons do.
Return true if you want to handle it. Return false if you want to pass it on.
If you return true, the framework highlights your object, waits three clock ticks, waits for the Return key to come back up, then un-highlights the object. Then Iowa calls your clickObject handler.
Only enabled objects are considered for handling the Return key.
For bold buttons, we thought about making it conditional on whether or not there was an active text object, but decided to leave this to the card designer. If you want to catch the Return key, use a bold button. If you want to pass the Return key into editable text, use a normal button.
We left this as a callback instead of a flag, because itÕs at least conceivable that the catching of Return keys might be conditional for some future object type.

Random Notes

setupconfig

Every IOA component must implement a routine called setupconfig.

You should fill in the fields of the config record, link in your callbacks. You can also initialize globals. The resource fork of your component file is open while your setupconfig routine is running, so itÕs a good time to load stuff from a resource.

Internal object types

Two object types, groups and clones, are implemented internally in the framework, since they are structural to cards, and rely on a much more intimate relationship with the Iowa framework than other object types.

Detail: Card Editor also knows how to paste a PicHandle into a picture object. If a picture object is selected when the user has selected a single picture object, the PicHandle replaces the picture. If nothing is selected, a new picture object is created.  We hope in a future version of the spec to remove this special knowledge of picture objects from Card Editor.

Two utilities from Apple

The Things! Control Panel is a must-have for component developers. ItÕs a simple browser for all components you have loaded. ItÕs available on AppleÕs Developer CDs.

Another utility, Reinstaller, allows you to drag/drop new component files onto it. They are installed. Be sure to close Things! before doing so, since it seems to make it crash. But this saves you from having to do a restart just to install a new component. ItÕs available on AppleÕs Developer CDs.

Note: at some point we will provide component management verbs for Frontier, or a UCMD, so it will be possible to do custom installers as scripts. Reinstaller is a nice start, but it really should be a droplet.

Component Manager access from Frontier scripts

You can register or unregister components from Frontier scripts using the Components UCMD. ItÕs in the Extras folder in the Iowa 1.0b14 release.

New feature in Iowa 1.0b14

Added an EventRecord to the definition of a hdlcard. This allows IOA components to pass on the record to a toolkit, such as QuickTime or the List Manager. ItÕs set in the main event loop of Card Editor and in the main event handler in Iowa Runtime. When your component is called, (**(**h).owningcard).macevent contains the last event received thru WaitNextEvent.