Scripting the Patcher

Your object can use scripting capabilities of the patcher to learn things about its context, such as the patcher's name, hierarchy, or the peer objects to your object in its patcher.

You can also modify a patcher, although any actions your object takes are not undoable and may not work in the runtime version.

Knowing the Patcher

To obtain the patcher object containing your object, you can use the obex hash table. The obex (for "object extensions") is, more generally, a way to store and recall data in your object. In this case, however, we are just using it in a read-only fashion.

Note that unlike the technique discussed in previous versions of the SDK, using the obex to find the patcher works at any time, not just in the new instance routine.

void myobject_getmypatcher(t_myobject *x)
{
t_object *mypatcher;
object_obex_lookup(x, gensym("#P"), &mypatcher);
post("my patcher is at address %lx",mypatcher);
}

The patcher is an opaque Max object. To access data in a patcher, you'll use attributes and methods.

Patcher Name and File Path

To obtain the name of the patcher and its file path (if any), obtain attribute values as shown below.

t_symbol *name = object_attr_getsym(patcher, gensym("name"));
t_symbol *path = object_attr_getsym(patcher, gensym("filepath"));

These attributes may return NULL or empty symbols.

Patcher Hierarchy

To determine the patcher hierarchy above the patcher containing your object, you can use jpatcher_get_parentpatcher(). A patcher whose parent is NULL is a top-level patcher. Here is a loop that prints the name of each parent patcher as you ascend the hierarchy.

t_object *parent, *patcher;
t_symbol *name;
object_obex_lookup(x, gensym("#P"), &patcher);
parent = patcher;
do {
parent = jpatcher_get_parentpatcher(parent);
if (parent) {
name = object_attr_getsym(parent, gensym("name"));
if (name)
post("%s",name->s_name)
}
} while (parent != NULL);

Getting Objects in a Patcher

To obtain the first object in a patcher, you can use jpatcher_get_firstobject(). Subsequent objects are available with jbox_get_nextobject().

If you haven't read the Anatomy of a UI Object, we'll mention that the patcher does not keep a list of non-UI objects directly. Instead it keeps a list of UI objects called boxes, and the box that holds non-UI objects is called a newobj. The "objects" you obtain with calls such as jpatcher_get_firstobject() are boxes. The jbox_get_object() routine can be used to get the pointer to the actual object, whether the box is a UI object or a newobj containing a non-UI object. In the case of UI objects such as dials and sliders, the pointer returned by jbox_get_object() will be the same as the box. But for non-UI objects, it will be different.

Here is a function that prints the class of every object (in a box) in a patcher containing an object.

void myobject_printpeers(t_myobject *x)
{
t_object *patcher, *box, *obj;
object_obex_lookup(x, gensym("#P"), &patcher);
for (box = jpatcher_get_firstobject(patcher); box; box = jbox_get_nextobject(box)) {
obj = jbox_get_object(box);
if (obj)
post("%s",object_classname(obj)->s_name);
else
post("box with NULL object");
}
}

Iteration Using Callbacks

As an alternative to the technique shown above, you can write a callback function for use with the patcher's iteration service. The advantage of using iteration is that you can descend into the patcher hierarchy without needing to know the details of the various objects that may contain subpatchers (patcher, poly~, bpatcher, etc.). If you want to iterate only at one level of a patcher hierarchy, you can do that too.

Your iteration function is defined as follows. It will be called on every box in a patcher (and, if you specify, the patcher's subpatchers).

long myobject_iterator(t_myobject *x, t_object *b);

The function returns 0 if iteration should continue, or 1 if it should stop. This permits you to use an iterator as a way to search for a specific object.

Here is an example of using an iterator function:

t_object *patcher;
long result = 0;
err = object_obex_lookup(x, gensym("#P"), &patcher);
object_method(patcher, gensym("iterate"), myobject_iterator, (void *)x, PI_WANTBOX | PI_DEEP, &result);

The PI_WANTBOX flag tells the patcher iterator that it should pass your iterator function the box, rather than the object contained in the box. The PI_DEEP flag means that the iteration will descend, depth first, into subpatchers. The result parameter returns the last value returned by the iterator. For example, if the iterator terminates early by returning a non-zero value, it will contain that value. If the iterator function does not terminate early, result will be 0.

Assuming the iterator function receives boxes, here is an example iterator that prints out the class and scripting name (if any) of all of the objects in a patcher. Note that the scripting name is an attribute of the box, while the class we would like to know is of the object associated with the box.

long myobject_iterator(t_myobject *x, t_object *b)
{
t_symbol *name = object_attr_getsym(b, gensym("varname"));
if (name)
post("%s (%s)",cls->s_name, name->s_name);
else
post("%s", cls->s_name);
return 0;
}

Creating Objects

Much of the Max user interface is implemented using patcher scripting. For example, the inspectors are patchers in which an inspector object has been created. The file browser window has four or five separate scripted objects in it. Even the debug window is a dynamically scripted patcher. We point this out just to inform you that creating objects in a patcher actually works (if you get all the details right). The xxx example object shows how to use patcher scripting to create an "editing window" similar to the ones you see when double-clicking on a table or buffer~ object.

Creating objects in a patcher generally requires the use of a Dictionary (see discussion of UI objects above), but there is a convenience function newobject_sprintf() that can be used to avoid some of the complexity.

To create an object, your task is to set some attributes. In the absence of any specific values, an object's attributes will be set to some default, but you'll probably care, at the very least, about specifying the object's location. Here is an example that creates a toggle and metro object using a combination of attribute parse syntax and sprintf. If you're interested in creating objects with newobject_sprintf(), it may help to examine a Max document to see some of the attribute name - value pairs used to specify objects.

t_object *patcher, *toggle, *metro;
err = object_obex_lookup(x, gensym("#P"), &patcher);
toggle = newobject_sprintf(patcher, "@maxclass toggle
@patching_position %.2f %.2f",
x->togxpos, x-> togxpos);
metro = newobject_sprintf(patcher, "@maxclass newobj @text \"metro 400\"
@patching_position %.2f %.2f",
x->metxpos, x->metypos);

Note that to create a non-UI object, you use set the maxclass attribute to newobj and the text attribute to the contents of the object box. Attributes can be specified in any order. Using the patching_position attribute permits you to specify only the top-left corner and use the object's default size. For text objects, the default size is based on the default font for the patcher.

Finally, note that newobject_sprintf() returns a pointer to the newly created box, not the newly created object inside the box. To get the object inside the box, use jbox_get_object().

Connecting Objects

If you'd like to script the connections between two objects, you can do so via a message to the patcher. Assuming you have the patcher, toggle, and metro objects above, you'll create an array of atoms to send the message using object_method_typed().

t_atom msg[4], rv;
atom_setobj(msg, toggle); // source
atom_setlong(msg + 1, 0); // outlet number (0 is leftmost)
atom_setobj(msg + 2, metro); // destination
atom_setlong(msg + 3, 0); // inlet number (0 is leftmost)
object_method_typed(patcher, gensym("connect"), 4, msg, &rv);

If you want to have a hidden connection, pass an optional fifth argument that is any negative number.

Deleting Objects

To delete an object in a patcher you call object_free() on the box. As of Max 5.0.6 this will properly redraw the patcher and remove any connected patch cords.

Obtaining and Changing Patcher and Object Attributes

You can use object attribute functions to modify the appearance and behavior of objects in a patcher or the patcher itself. Note that only a few of these attributes can be modified by the user. The C level access to attributes is much more extensive.

Attributes whose type is object can be accessed via object_attr_getobj() / object_attr_setobj(). Attributes whose type is char can be accessed with object_attr_getchar() / object_attr_setchar(). Attributes whose type is long can be accessed with object_attr_getlong() / object_attr_setlong(). Attributes whose type is symbol can be accessed via object_attr_getsym() / object_attr_setsym(). For attributes that are arrays, such as colors and rectangles, use object_attr_getvalueof() / object_attr_setvalueof().

Patcher Attributes

Name Type Settable Description
box object No The box containing the patcher (NULL for top-level patcher)
locked char Yes (not in runtime) Locked state of the patcher
presentation char Yes Presentation mode of the patcher
openinpresentation char Yes Will patcher open in presentation mode?
count long No Number of objects in a patcher
fgcount long No Number of objects in the patcher's foreground layer
bgcount long No Number of objects in the patcher's background layer
numvews long No Number of currently open views of the patcher
numwindowviews long No Number of currently open window-based views of the patcher
firstobject object No First box in the patcher
lastobject object No Last box in the patcher
firstline object No First patch cord in the patcher
firstview object No First view object in the patcher
title symbol Yes Window title
fulltitle symbol No Complete title including "unlocked" etc.
name symbol No Name (could be different from title)
filename symbol No Filename
filepath symbol No File path (platform-independent file path syntax)
fileversion long No File version
noedit char No Whether patcher can be unlocked
collective object No Collective object, if patcher is inside a collective
cansave char No Whether patcher can be saved
dirty char Yes (not in runtime) Whether patcher is modified
bglocked char Yes Whether background is locked
rect double[4] Yes Patcher's rect (left, top, width, height)
defrect double[4] Yes Patcher's default rect (used when opening the first view)
openrect double[4] Yes Fixed initial window location
parentpatcher object No Immediate parent patcher (NULL for toplevel patchers)
toppatcher object No Topmost parent patcher (NULL for toplevel patchers)
parentclass object No Class object of parent (patcher, poly~, bpatcher etc.)
bgcolor double[4] Yes Locked background color (RGBA)
editing_bgcolor double[4] Yes Unlocked background color (RGBA)
edit_framecolor double[4] Yes Text editing frame color
locked_iocolor double[4] Yes Locked inlet/outlet color
unlocked_iocolor double[4] Yes Unlocked inlet/outlet color
boguscolor double[4] Yes Color of uninitialized (bogus) objects
gridsize double[2] Yes Editing grid size
gridonopen char Yes Show grid on open
gridsnapopen char Yes Snap to grid on open
imprint char Yes Save default-valued object attributes
defaultfocusbox symbol Yes Default focus box (varname)
enablehscroll char Yes Show horizontal scrollbar
enablevscroll char Yes Show vertical scrollbar
boxanimatetime long Yes Box animation time
default_fontname symbol Yes Default font name
default_fontface long Yes Default "fake" font face (0 plain, 1, bold, 2 italic, 3 bold italic)
default_fontsize long Yes Default font size in points
toolbarvisible char Yes Show toolbar on open
toolbarheight long Yes Height of toolbar (can use 0 for invisible)
toolbarid symbol Yes Name (in maxinterface.json) of toolbar, none = empty symbol

Box Attributes

Name Type Settable Description
rect double[4] Settable only Changes both patching_rect and presentation_rect
presentation_rect double[4] Yes Presentation mode rect
patching_rect double[4] Yes Patching mode rect
position double[2] Settable only Changes both patching_position and presentation_position
size double[2] Settable only Changes both patching_size and presentation_size
patching_position double[2] Yes Patching mode position (top, left corner)
presentation_positiond[2] Yes Presentation mode position
patching_size double[2] Yes Patching mode size (width, height)
presentation_size double[2] Yes Presentation mode size
maxclass symbol No Name of Max class (newobj for non-UI objects)
object object No Associated object (equivalent to jbox_get_object)
patcher object No Containing patcher
hidden char Yes Is box hidden on lock?
fontname symbol Yes Font name (if box has font attributes or a text field)
fontface long Yes "Fake" font face (if box has font attribute or a text field)
fontsize long Yes Font size (if box has font attributes or a text field)
textcolor double[4] Yes Text color (if box has font attributes or a text field)
hint symbol Yes Associated hint
color double[4] Yes Standard color attribute (may not be present in all objects)
nextobject object No Next object in the patcher's list
prevobject object No Previous object in the patcher's list
varname symbol Yes Scripting name
id symbol No Immutable object ID (stored in files)
canhilite char No Does this object accept focus?
background char Yes Include in background
ignoreclick char Yes Ignores clicks
maxfilename symbol No Filename if class is external
description symbol No Description used by assistance
drawfirstin char No Is leftmost inlet drawn?
growy char No Can object grow with fixed aspect ratio?
growboth char No Can object grow independently in width and height?
nogrow char No Is object fixed size?
mousedragdelta char No Does object use hidden-mouse drag tracking (number box)
textfield object No Textfield object associated with this box if any
editactive char No Is object the currently focused box in an unlocked patcher?
prototypename symbol No Name of the prototype file used to create this object
presentation char Yes Is object included in the presentation?
annotation symbol Yes Text shown in clue window when mouse is over the object
numinlets long No Number of inlets visible
numoutlets long No Number of outlets visible
outlettype symbol[] No Array of symbols with outlet types ("signal" etc.)

To access an attribute of a non-UI object, use jbox_get_object() on the box to obtain the non-UI object first.

  Copyright © 2015, Cycling '74