A spec describing plug-in architecture for new MacBird object types.
Originally written in 1993 by Dave Winer, dwiner@well.com.
MacBird, which is part of Frontier 4.1, is a UI designer and runtime for colorful "cards" containing Frontier scripts. Even better, it's got a plug-in architecture for new object types. So gutsy C programmers can develop new objects. This page tells you how to do it.
The IOA Spec
IOA is an acronym for Interactive Object Architecture.
It's a specification and a toolkit.
The purpose of IOA is to open up MacBird so that people can add new object types.
While MacBird was in development it was called Iowa. When you see "Iowa" on one of these pages you should think of "MacBird". We had to leave it this way because all the source code uses the Iowa name. It's deeply engrained. Even the creator id of the MacBird app is IOWA. So on this side of the fence, among C programmers, this stuff is called Iowa.
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.
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.
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.
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 types are 32-bit signed numbers. To avoid the need for an object "reunion" utility, IOA developers should ask for a unique object type on the "mailing list", 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 in framework
checkboxtype = 2
radiobuttontype = 3
picturetype = 4
statictexttype = 5
edittexttype = 6
buttontype = 7 //note two missing types
recttype = 10
linetype = 11
ovaltype = 12
icontype = 13
formulatype = 14
popuptype = 15
clonetype = 16 //implemented 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:
o All objects of this type are turned off, their objectflag is set to false.
o 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.
o 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:
o It sets the tracking field of the card record to true.
o 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.
o 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.
None of the UserLand-supplied objects (1/19/93) set this bit.
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.
See "ioa.h" for the declaration of tyobjectrecord.
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.
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 Open 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"}.
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 object type.
IOA Randomness
Here are some 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.
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.
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.
© Copyright 1996-97 UserLand Software. This page was last built on 5/7/97; 1:36:21 PM.
It was originally posted on 8/16/96; 2:47:53 PM.
Internet service provided by Conxion.