Mac OS • 1:05:04
Want to make your application scriptable, but can't figure out how? Learn how to use Metrowerks PowerPlant and other frameworks to make your application scriptable.
Speaker: Chris Espinosa
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
I'm Chris Espinosa. If you were at the earlier session, the AppleScript overview session, you've heard a lot from me. If I press the forward button and the machine just bings, what does that mean? This session is going to be a lot different than the overview this morning. This is a very code-oriented session.
If you are looking to learn how to script in AppleScript, now would be a fine time to debark this aircraft. We are going to be talking about C++ code. We're going to be talking about classes. We're going to be talking about a lot of things that are only of interest to people who are writing applications that want to be scriptable.
If you are a scripter, don't hurt yourself by trying to listen to me. Go to www.apple.com/applescript and download some of Sal Sehgoyen's very, very fine AppleScript guidebook modules on how to write AppleScripts. For those of the rest of you, if you want to learn how to make a scriptable application in C or C++, you're in the right place.
Good. What you're going to learn in this session is why there are relatively few good scriptable applications on the Macintosh platform, because it's hard. There are a lot of bad scriptable applications on the Macintosh platform because it's easier to make a bad scriptable application than a good scriptable application.
Frameworks can help. Frameworks can help by doing a lot of the work for you. Frameworks can hurt. Frameworks can hurt because you assume they're doing a lot of the work for you when they're not. So we're going to tell what it does for you versus what you have to do yourself.
We're going to talk a lot about PowerPlant. There has been a very good PowerPlant solution out there. Greg Dow put a lot of work into making some good scripting classes in PowerPlant. And it's the most widely distributed and widely used application framework for Macintosh. So we're going to be focusing a lot on that. And then we'll be covering some alternatives.
One of the alternatives I want to stress is a relatively new one, which DTS put up just last week as the More OSL package. If you're familiar with More Files, More Apple Events, More Toolbox from DTS, which basically fills in for developers the functionality that developers want in the OS that the OS designers didn't put there, well, this is another in that long and esteemed series of software codebases that does just that.
DTS has done a wonderful job of responding to what the most frequently asked developer gripes about how to write scriptable applications are and filling that in in a very coherent and manageable way. It's a great piece of work, and I'm going to be walking you through that. Let's start with some background, because I know that some of you are approaching scriptability for the first time.
AppleScript and the Open Scripting Architecture, OSA, is a way for a high-level language, a user-written script, to send events to applications so that applications can act on those events and execute them. And the biggest problem that we find, the biggest sin that is committed, is applications that simply expose a flat programmer's API to the scripting language.
A whole lot of events and not a lot of information. Because what we've found over the years with what is popular with scripters in AppleScript is that they don't just want to send a bunch of events to applications. They want to get and set information within that application.
And the information they want to get and set is the information on the user's model, the conceptual model of the information inside that application. Rather than clicking a checkbox or pushing a button or choosing a menu item, they want to get the value of something or they want to create a new user object somewhere in the document. It's not, you know, an automation like a macro maker or a playback or recording.
It's more like we want to make queries on the data in your application, make intelligent decisions based on that data, and then do something that manipulates the data, not manipulating the application. Because most of the really, really good process automation and workflow solutions that are built using AppleScript are very, very data-driven, not command-driven.
And this is why the object model is so important. And what is the object model? The object model is the way that OSA languages access the user-visible information in your application. And it applies a human terminology to the programmatic elements inside your application. The two biggest things people do is, one, is that they just provide an event interface and no object interface so that the users can't see the objects because of all these events. And two is that they fail to give human names to the objects within their applications and just basically propagate the C++ identifiers up to the scriptor levels.
And that's confusing to people. An object is-- can be, but is not necessarily-- your concept of a programmatic object inside your application. If you look at the model view controller paradigm for developing an object-oriented application, what we're really talking about is the model. We're not talking about the view. We're not talking about how it's drawn on the screen. And we're not talking about the controller-- how it's manipulated.
We're talking about the existence of the object and the properties that that object has. Those properties can be read and set, preferably from the scripting language. It's of a designated class, so it can be told apart from other things of that class. And that class may be in a hierarchy, and it can share some things or inherit some things from other classes.
It can be a container of other objects, like a window can contain data elements, or a folder can contain files, or a page can contain graphs. Graphic objects or a paragraph can contain words or characters. And it can be contained by other objects as well. We script objects, and the best scriptable applications are those that use as few verbs as possible to script all of the objects in your applications. That's a subjective statement, but I lay claim to it.
The way that AppleScript and OSA languages designate to your application what they're trying to act on is through this structure called an object specifier. And an object specifier is possibly the most heinous data structure you're going to come across in Macintosh programming. It can be incredibly complex. It can be vague and ambiguous. It can be very detailed in inappropriate places.
And there are 247 different variants that you have to special case to make sure that when somebody sends you an object specifier, you understand really what they're talking about. Because what it does is it drills down from the top level of your application down to a specific piece of data in your application and can get there through multiple routes. And you don't necessarily have to have a strict containment tree. So it can really go through pretty bizarre ways. Let me show you, I'll show you a little later how complex and how weird object specifiers can be.
The way you get an object specifier is as an Apple event record that is deeply nested. It's going to look on the top level like, "Oh, this is easy. It's an Apple event record with four parts to it." But then one of those parts is an object specifier which has four parts to it, which can be an object specifier that has four parts to it. And it can go on, down, and down, and down.
The four parts are basically the container of the object, that's the from part, the type of data that's being requested, which is the want, how to select a value from the container, which is the key form, and we'll be talking a lot about key forms, and then using that key form, which one to pick.
So if I say I want folder one of startup disk, that's basically from is startup disk, want is a folder, form is by absolute position. Chris Espinosa And then the other part is the data that's being requested, which is the want. Chris Espinosa So that's the from part. The type of data that's being requested, which is the want.
How to select a value from the container, which is the key form, and we'll be talking a lot about key forms, and then using that key form, which one to pick. So if I say I want folder one of and selection data is one. That's a fairly simple object specifier, but they can be more complex.
For example, the object specifier, the modification date of file 2 through file 4 of folder applications of startup disk is nested kind of like this. In the top level on the outside, you're looking for the property. You want a property. The selection is the code for that property, which is the modification date.
And the container is, you want the property of a file. So the next thing is, you want a file selected by the key form range. The key form range is going to make two parts come. You want the selection data, which itself is a record with a start and a stop, and the start's going to be, you want form index, you want a file from the current container index 2, and the stop is going, you want a file index, that's actually wrong, that should be index 4.
See how easy it is to make a mistake? So you want start at 2, stop at 4 from the current container, and then the last thing, the from goes all the way down to the bottom there, and is by name, the selection, a folder, the selection data is, quote, applications from the startup disk. And the startup disk's container is null, which means it's rooted at the app, it's a from an Apple script.
And then this is a parameter to an event, which can be, get the modification date, set the modification date, or this could be just a clause in a, you know, delete every file, who's modifying it, who's modification date, you know, et cetera. So, it's, object specifiers can be arbitrarily complex, and if you tried to write code yourself to parse this, you'd go fairly nuts. I know, because people have.
Here are the basic key forms. One of the reasons is the combinatorial problems of all of your containers against all of your objects versus all of the key forms. And so, before you go into that, you really need to know what the key forms are. The most familiar one is by numeric index, file one, first page.
That's the form absolute position. And the selection data you're going to get is usually an integer, which is very nice. Now, one of the things to remember is that you are in control of this. So you can be expecting any selection data you want in form absolute position. So you could make form absolute position any arbitrary thing. You could make it a string. As long as it identifies what you're talking about, AppleScript will tend to do that in the form absolute position.
If you say window quote untitled or folder quote preferences, that's going to trigger the by name key form, form name. And your selection data for that is usually a string. And it's usually going to be a string in the system script. We don't have Unicode selection yet for that, so it's going to be a string in the system script.
Those are the two main ones. And if all you support is by numeric index and by name and you support them well, that's okay. For certain classes, Unique ID is going to be interesting, but it really had better be unique. Form Unique ID means I want this thing regardless of what its index is because its index may change and what its name is because its name may change.
I mean, think of this. If you're writing a script that's trying to script a finder folder, you're going to have to write a finder folder. And you want to change its name. And when you change its name, its index changes in the list because the finder automatically resorts the list by name.
How are you going to refer to that thing again if you've changed its name? And if you don't know what the name is and don't know what the new index is, you're really going to need to hold on to something, and that's what the Unique ID is for.
So what you'd write in the finder is set X to ID of folder foo. Set name of folder foo to... bar. And then it's no longer folder foo, it's folder bar. And its index is different, but the ID is the same. And then you can say folder ID M, and you'll be able to work on that folder again.
Those are the simple key forms. The more complex ones to handle are before, after another element, that's relative position. This is how you insert things in the list or append things to a list. It's usually used with text. That's form relative position. And form relative position is basically a tuple of an absolute position or any other reference, any other object specifier. And then a tag that says before or after.
As a range of items is a pair of the start element and the stop element, which both can be any arbitrary object specifiers. And that's how you do page one through three in Quark, for example. And then satisfying a test, which is the most interesting one. It's every folder whose name starts with or ends with .jpg. A very useful thing to do in the finder. That's the form test and the form whose.
In order to make your object model really usable by all of these, all of these key forms can be compiled by the Apple script language. And so users can use these. You should document in your scripting terminology in your dictionary which objects support which key forms, just to not mislead people. Apple script will pay no attention to that.
It will try to compile all of these key forms regardless of what you say in your terminology. It will generate these object specifiers. It will send you a message that says, "This is a good idea. I'm going to use this." And then you can use it to make your application scriptable. So, you can use this.
And then you can send them to your application and your application will either say, "I don't support this key form," or do it right. Now too often, applications don't say, "I don't support this key form," and they don't do it right. And they just take a wild stab and return an error.
And what we--if there's one thing I'd like to come out of this session is people at least being honest with scripters, both in their terminology and in their implementation, what key forms are supported on what objects. And that's a hard thing to do because you have to be honest with the scripter. have to know them all and know all their combinations.
Because all of the example, users can come up with wonderful, wonderful phrases in AppleScript that your application just can't handle. Like, name of every track of movie one who's enabled is true. Or modification date of every file of folders one through two of every folder whose name starts with A. All of these generate valid object specifiers. And your application, if you say you've got an object model and you've got all these objects, you should be able to handle things like this.
How do you handle this? The process of handling these is something called object resolution. And basically, when an event comes in and there's an object specifier in its parameter, your first task is to resolve that object to some indication of what the thing is in your application that that object specifier represents. What you have to do is you have to descend in the structure until you find that from null, which means, oh, that's the root object.
And then you have to back out, looking at each key form and selection data in turn, and then maybe walking back down and doing it again if it's like a range form and you need to descend into those object specifiers and figure out what they mean. Descending from that root, just basically unpack the object specifier until you get back to the O.
And what they eventually want is the modification date or the file or the whatever. So when you walk in, you find null and then you walk out until you get to the outermost object specifier and then you're done. But that's a hard process. It's recursive and it's got a lot of different cases.
Sometimes, the intermediate thing that's generated will be a list, like a range, so that at the next level, you have to iterate over that range and do the same thing to it, like the modification date of every file. You can find every file and then you have to iterate over every file to get the modification date of that. There are a lot of rules that you have to follow. have to follow in order to do it right.
They're arbitrarily complex. You have to have basically a three-dimensional matrix of switch cases. You have to have a switch for every containment relationship. You have to have a switch for every key form supported by each containment relationship. And you have to have a switch for every potential data type of the selection data.
You know, the form name could have a name. It could have--the absolute position could be an integer. The absolute position could be something other than an integer. Your ranges could be object specifiers. They could be absolute position. And there are no good general rules about how to handle the weird corner cases. A lot of this stuff isn't written down anywhere.
[Transcript missing]
So what do you do if it's so hard, if it's so complicated, and the system software gives you so little support? What do you do? Well, you do it badly, which is what most of you have done. And I don't blame you. But there are tools now that help you do it well.
If you're scripting with the framework, the general process is that you create model objects in C++ that reflect your user's view, your scripter's view of your application. So if you have a graphic object concept that a user can draw a graphic object, you make a graphic object class in C++. And maybe you don't really have a graphic object class in C++ in your implementation, but just create one whose implementation then specializes to circle, square, line, or whatever. But create something that you can hang off scripting.
Then the general paradigm, and this is used in almost every framework that offers scriptability, is that any incoming event, like make new, delete, move, whatever, that operates on an object will become a call to a member function on that object. And any Apple event properties, like the size, the dimensions, the visible, the color, that a scripter can get at, are just represented as data members of that object. And then your element, your containment relationships, become collection class collections on that object. And so basically what you do is, what I'd recommend doing is you start with your user model.
Create your terminology. Then design a bunch of C++ objects that implement a C++ view of that terminology. Wire them up on the top side to the scripting engine of that framework, on the bottom side to your actual implementation object, and then try it out and see how it works. And you may have to do a little tuning to get it all right. But that's the order that I really recommend you start in. Start from the user model of your application. Design your scripting terminology.
Implement C++ objects that are structured like that terminology. Then wire them up to the scripting engine, and then wire them to your actual implementation, so that the script commands can flow all the way from the scripter's view of the world to your actual objects in your C++ application.
And so this is how it works in PowerPlant. If you get PowerPlant off the shelf, and this has been true for years, what you can do is if you have a C++ class and you derive it from the L model object class, that becomes the equivalent of an Apple event object model class.
And so in your terminology, if it has class window, if you look in PowerPlant, the window class in PowerPlant derives from L model object, and so it looks like a window to AppleScript, the AppleScript window class. The events are member functions, so just as the window L model object in PowerPlant has a lot of internal C++ method functions, that member functions that it responds to.
It also responds to member functions that correlate to the standard suite of Apple event objects. Make new, delete, for Windows it's close, Save, Save As. So there's a correlation between the events that come in and the member functions on that class. The parameters are arguments to those member functions. Properties of objects correlate to the data members on those classes.
PowerPlant implements the containment relationships using their collection classes called LLists. And so everything, every object that descends from LModelObject has a number of LList, has basically an LList attached to it, which is all the things that that object contains. And so a window, if you have a window that's populated with widgets, and widgets also descend from LModelObject, then your window class is going to have an LList of widget objects. And then each widget object is going to have a data member that's called mSuperModel, and that's going to be the window it belongs to. So that's how you do the containment relationship with the LList on the container and the mSuperModel on the contained.
And this makes it very, very easy once you have your object model designed and your terminology designed to go in and write all these C++ classes that derive from L model object and have L lists and have M supermodel data members. And that makes your internal structure resemble the terminology that you've defined.
And PowerPlant helps you out a lot in this by doing some of the stuff automatically, especially the event registration. Basically, first you start with your terminology resource. And I hope all of you have learned how to create and edit a terminology resource. We could have a special clinic just on that because it's pretty hard. But you add an Apple event object model class to your terminology resource and you invent a four-character code for your class.
A WIDG for your widget. Then you create the C++ class that has L model object as a public ancestor. And then you redefine the constructor on your WIDG object, on your widget object. So it takes an L model object reference to its container. And you pass that pointer to the L model object constructor. And when somebody says make new widget... Your constructor gets called, and it just happens automatically. It's very nice. It's a very clean system. In the constructor, you set the model kind to that four character code that you've defined in your terminology, and that's how it gets linked.
And then in each container that has that class as an element, this is the containment relationship, you call set uses submodel list to true, and that says, oh, I'm a container. I contain these things. And then you'll get method calls, you'll get member function calls on your objects that correspond to Apple event calls. So it's sort of like handle, if somebody makes a new thing of you, like if you say make new widget, that container object will get a call handle create element.
And that says, oh, you know, this container, the window, wants a new widget created in it. Then there you go. Here's the new widget object that's been created. Here's the container that it's being created in. Do what you need to do in your low-level code to draw it or initialize it or whatever you need to do.
Once the object exists, then getting and setting the class properties are very straightforward. You, again, define your properties in your AETE. You create a four-character code for each property on each object. And then there's a method on each class called getAEProperty. You just override that method, and you switch off the four-character code, and there you go.
So if you have a widget that has color, size, shape property, you have three four-character codes for those. Your getAEProperty method will be called on your widget class. It'll be called with the parameter that is the property code. You just switch off that and say, oh, if I'm getting the size, I return this.
If I'm getting the shape, I return this. If I'm getting the color, I return this. Then you can go in and do the appropriate manipulation. To get the right data, you create an AEDesk with the value. And then you return that as the return value of getAEProperty. It's very straightforward again.
Here's an example of it. I mean, you switch on the in property, do three cases. This is contents filled and line width, for example. And you just get the appropriate value straight from the object, or you synthesize it if it's necessary. And then you call aecreateDesk to put it into the right form that correlates to the class that you defined it as in your terminology. You know, if your terminology said that your contents are a QD rectangle, R-E-C-T, then you'd better return it as a rectangle. That would be good.
Setting class properties is similar. What's going to come in to your setAeproperty is a property switch and a value in an Aedesk. You write a switch statement that switches off the in property. Then you extract the value from the Aedesk and coerce it, if necessary, to the right one. And then you set it. And if it can't be set, you return an error. If the set is successful, then you return silently.
So here's a set AE property switch. Notice something in here. There's a refresh call. Basically, if your application has a user interface, when a scripter sets properties on an object, you may want to update your visual display. You may want your model to talk to your view to refresh the image. If somebody set the bounds of something, you wanted to redraw it. So you have to call refresh to redraw it.
How do key forms work in PowerPlant? PowerPlant implements most key forms for free. Because L lists are indexed lists, PowerPlant knows how to get widget four of window two, because it just goes down the L list and gets the fourth widget from that and sends them to that object. Form name works if you have a P name property. It's special case if you have a name property, form name works. But everything else you really have to do yourself.
There's this method called get model token self which passes in the key form and you pretty much have to figure out from there how it works. Mostly they're pretty easy. There's some sample code on the web, I can give you a reference to it, that implements form range.
Doesn't implement it well, but it basically implements it. Chris Espinosa What's the key form? L lists are indexed lists. PowerPlant implements most key forms for free. Because L lists are indexed lists. PowerPlant implements most key forms for free. Because L lists are indexed lists. PowerPlant implements most key forms for free.
Because L lists are indexed lists. PowerPlant implements most key forms for free. Because L lists are indexed lists. PowerPlant implements most key forms for free. Form unique ID is fairly straightforward. Whose clauses are kind of difficult though? And how do events work? Well, the good news is that PowerPlant implements, exists, make, delete, count, duplicate, and move, the basic standard events, for any object that descends from lmodelobject. So these come for free, which is really nice.
You just have to implement the constructors and destructors. Duplicate is fairly simple. The difficult part for duplicate is that you've got to set the initial, like make new, you've got to set the appropriate initial values for, so for duplicate, you've got to get the, you've got to get the initial values for the initial values.
Get the properties that are important of the object you're duplicating and then set them. You know, if you duplicate an object, all the properties are not going to be identical. The index will be different. The unique ID will be different, for example. So, if you're duplicating a widget, you need to get the important properties of widget, of the old widget, and then set the properties of the new widget to that set, skipping the ones that are defined to be unique, like the name perhaps, like the index, like the unique ID.
And then you've got to handle createElement event in each container. You know, create has to happen in two place. If you're creating a widget in a window, the widget has to know it's being created. And the window has to know that a new widget has been created in it in order to get all the updating to happen right.
Here's some code, and this will be on the slides on the CD if you want to read it, if it's too hard to read up here. But basically, this is the create element event. You switch on what class you're creating. If your container can handle multiple different classes created in it, then if it's... If you get a create event to create this, then you do a new that. If it's that, then you do a new this. If it's not, then you throw an error.
Now what about custom events? I mean, it's all well and good to have an application that is just objects in the standard events. And I really, really applaud you if you can make an application that is nothing but objects in the standard events. But you might want to do your own custom event.
I mean, the finder does for like restart and shut down. That's pretty easy too. Once again, you start by defining the events in your terminology resource and inventing a four character code. And it's actually a pair of four character codes, the event class and the event type. What you do in PowerPlant is PowerPlant has a resource called the AEDT resource. And the AEDT resource is nothing but a table that maps event class code pairs. Okay.
To long ends. And that's all it is. It's just a table of event class code and long end. And what happens is That when an Apple event comes into a PowerPlant application, it gets looked up in this table, the long end is generated, and then you switch off of that long end.
Because it's too hard to switch off of two 4K codes, so it basically switches off the long end. So if you want to create a custom event, you know, like a refresh event or something like that, you invent the class creator code for it, you associate it with a number, and then in the switch statement in handleAppleEvent, you switch off of that number, and then when that case is called, you just execute your code there.
And you do that on every object that can handle that event. So if every object has its own refresh event, you might want to have that handleAppleEvent method and have that case in the handleAppleEvent method for each object that handles a refresh event. Or you might want to have it, for example, if you've got a number of objects that can be refreshed, you might want to just do it once and have all the other refreshable objects inherit from that so that you just assume the inherited capability.
And in many of the cases for the standard events, that's exactly what PowerPlant does, is it doesn't implement it in the object itself, but it just hands it off to the class that it inherits from. So here's an example. Here's a rotate event. You switch on the AppleEvent number. If the case is 8erotate, then you call rotate. If not, then you call the inherited handleAppleEvent so that you pass it up. to the superclass to see if it can handle it.
Here's some things to remember about PowerPlant. Number one is that PowerPlant applications are, by default, scriptable in the most stupid possible way, in that they support a small number of standard events on the window class only, but they expose a full, in some cases too full, Apple event terminology that promises more than it actually delivers.
So, for example, if you just build the demo PowerPlant application, it says it supports make new, delete, count, create, whatever, on a whole bunch of objects, and none of that support is actually in the code, and that's bad. So the first thing I want you to do is to trim or throw away the terminology resource in your PowerPlant application. Treat it as if it's just... You know, start from scratch.
design your scriptability, then write a terminology resource from scratch, and then implement that terminology that you've defined. Because if you don't do that, what's going to happen is that the default terminology resource that gets compiled into your Power Plant application is going to promise that it supports all these events on all these objects, which just aren't there. And it's a source of great frustration for a lot of scripters that, you know, they get a new application and the first thing they do is they drag it onto the script editor to see whether it's scriptable.
And this dictionary pops up and it's great. Oh, look at all these events. They're the standard suite. They've got all these objects. And you look at it and it's just Power Plant. And that's all it is. And then you know it's not going to work. So please trim your Power Plant dictionary.
The second thing is, I cannot possibly do PowerPlant scripting justice in the time I have in this presentation. But luckily, on CodeWarrior Pro and actually every version of CodeWarrior back for several years, there's been a very good introduction to doing scriptability in PowerPlant. In the PowerPlant Advanced Topics book that's in PDF form on the PowerPlant documentation CD, in the CodeWarrior documentation CD.
It's there. It's good. Read it. Study it. These slides were basically stolen shamelessly from there. It's good documentation and it will help get your head around the whole Apple Events thing. So, if you want to code in PowerPlant, go to PowerPlant Advanced Topics, trim the AET resource, start with designing what you want the user to see, build your terminology, build some C++ objects that reflect that, then wire them up. Both to the scripting interface through L model object and then to your actual implementation. That's the way to go about it.
But what if I don't want to use PowerPlant? Well, you've got options. One of the things you can do is you can manually extract LModelObject and the LList class and some of the other classes that are hanging on. You can just extract them from PowerPlant and compile them into your application. Now there are a lot of little threads that you may have to pick up of things that PowerPlant depends upon, but the PowerPlant classes are extractable from PowerPlant itself.
So you don't have to use their window model. You don't have to use their drawing model, their load and save model. You can just extract the classes and adapt them and use them as a model in your own application. Could be a lot of work, but it does save you a lot of work. So that's one thing you can do.
There's another framework, an old venerable framework called Sprocket by Steve Sissick that hasn't been updated for I guess four years now, but it's still available online at CodeWell.com, I believe. And it supports some decent Apple event scripting. It's some helper classes. There's a lot of other things that help you get your head around it. It's not terribly up to date, but it's a start.
If you want to commit to using Objective-C or Java and deploying on Mac OS X only, you can use the Cocoa framework. There's some very good scriptability in Cocoa, and you can go to the Cocoa Advanced Topics presentation, which is at 3 o'clock today, to talk about that. Or you can use the Mac App Framework, and I'm sure Tom Becker will be very happy to show you what scriptability is in the Mac App Framework.
But there's one more opportunity, which has just presented itself, coming from our DTS group called MoreOSL. MoreOSL is not a framework per se. It's more like a support library that you can use from a C application or a C++ application. It's carbon-ready, which if you want to deploy on Mac OS 9 and Mac OS X is very nice.
It'll get you there faster. And it deploys all the way back to Mac OS 8.5 and compiles all the way back to Cocoa. And it's also available on the CodeWarrior Pro 2. So those of you who are sticking back on earlier versions of CodeWarrior and don't want to use the latest and greatest are not cut off from this functionality. It supports, it really does support doing a good object model. I really like the way that it goes about doing it.
And it's comprehensive, it's tested, and it comes with a good sample application that shows you how it works. And I'm going to be demoing that in a little while. You can get it on developer.apple.com slash sample code. It's a little bit more expensive. It's a little deeper in there, but at the moment there's a top-level link to it. Very small, quick download. Go for it.
Here's the theory of operation. It's a little different from the way the PowerPlant thing works. You have to define your user classes and your user events, and I recommend you do that the same way. You start with the user model, and then you define your terminology, and figure out what your object model classes and object model events are going to be. But then you make tables. You make a table of all your classes, and you make a table of all your events, and then you make a table for each class of what events apply to that class. and you build those in straight C structs.
Fans of straight C structs. You create an event table entry for each event on each class. And it maps the event codes to the classes they operate on. And then it also has some bits that declare what's supposed to happen with the direct parameter, whether it's required or optional, whether this generates a reply or not, things like that. So you set a couple flags in these tables. So basically, the first thing your application does is creates all these tables that defines its scriptability.
Then, you, then, more OSL does all of the registry with the object support library and with the Apple event handler for you. It registers its handlers, so you don't have to. So, when an event handler gets called or an object accessor gets called, more OSL takes control, and it will call back to you when it needs to, but you don't have to interact directly with the operating system, which is very nice.
And in many cases, the generic handlers, the generic event handlers from MoreOSL will do what you want with no further intervention, which is actually pretty cool. When your application processes an Apple event, MoreOSL resolves the direct parameter. It calls OSA Resolve for you. It dispatches the event to the event handler for your class directly. It's class-first dispatching. It's the right way to do it. And then the generic handler handles the event unless you've defined a specific handler for that event on your class, in which case your specific handler gets called.
So let me show you just basically a class table entry. There are three main parts to it. There's an event ID, which is, you know, here's the event that's coming in. And then a couple of pointers to other tables, a table for the properties and a table for all the events on that class.
And then for each class, you must supply a couple of callback routines. And you can supply others and just leave them null if you're not supplying them. For each class, you must write a counter routine that counts how many of those classes there are in a container. And then you can provide accessors that are get a class by unique ID. You know, get me from my container by unique ID, by index, by name.
And then there's get me or something of me. There's a set me or something of me. And then there's a coerced token. And the tokens are pretty much the way that the OSL works. The token is a representation of the internal object. And tokens in more OSL are basically just pointers. They can be a class pointer. They can be a pointer. They can be a handle. But it's basically 32 bits that uniquely identify something in your application.
So then there's an event table entry, and the event table is very straightforward. It's just the four character codes for the event and the event class, whether or not the direct object is required, and whether or not there's a result and what action to take on the result. And then in each class, there's a table for where's the handler for the exists event, the count event, the get data event, and the set data event.
And then as you add more custom events to the event table, you have to provide corresponding events in the class event handlers. I mean, it's really dumb dispatching. The event table, the master one, and every class event table have to be in parallel. That is, if you add a rotate event to the event table entry, you have to add a rotate event in the same position in every class event handler, even if many of them are null because you can't rotate those. But the one you have, one that does support rotate, that's the pointer to the rotate routine. Okay? So the class event handler table should look like the event table entry customized for each class.
And then the property table off of each class is very straightforward. It's property code and a bunch of flags that say what it does. And the property access is actually done through the class's getter setter event with the same kind of switch statement. If you provide a pproperties property, a properties property in your terminology, which is the way things are done now, more OSL will automatically iterate over all of the properties in your property table and get them or set them on NAS, which is great.
It saves you the trouble of doing that. If you provide a pinherits property in your terminology and you put it in your property table entry, more OSL will automatically look in your container classes for properties that you don't support directly. So if I have, like in the finder, there's an item and a file. And items have names. Files don't have names. Files have modification dates, but they inherit names.
So in the... In more OSL parlance, there won't be a property table entry for name in the file object. And more OSL will see the inherits and say, "Oh, if I'm looking for the name property, I don't look in the file object. I look in the item object that it inherits from, and it'll do the right thing for you." Which is really very nice.
In terms of event handler, moreOSL does exist, count, getObject, and setObject for you. It implements those generically, and in most cases, you really won't need to do anything to change or override those. Most of the key forms are handled automatically, and this is good for two reasons. One is that you just don't have to implement like form range and several of the form test cases.
And second is you can read the source code and see how it ought to work, if you so desire to see how it ought to work. It's really great. The only one that's not handled automatically in the current version of OSL is the form relative position, before, after an event.
Let me talk a little bit about tokens. Like I said, tokens are the way that you relate a resolved object specifier to something in your application. And a token is basically a token code, an object code, a property code, and then a pointer to something. And this is a simple token definition. That pointer to something can be like a window pointer, a pointer to a toolbox structure.
It can be a pointer to one of your C or C++ data structures, or it can be a C++ object. It's just 32 bits that your application, if given a pointer, can say, oh, this is a pointer, and the object code reminds me what it's a pointer to. That means I can do something with this data. I can create it, I can delete it, I can get information from it, whatever.
The only important thing about a pointer is that your application understands what it is. A token. Tokens never leave your app. They're never sent back to Apple Script. They're never sent to anybody else. So all that a token has to do is be creatable by and understandable by your application.
Properties, getters and setters. It's very straightforward. If somebody gives you a property token, your getter ought to return an appropriate value. If somebody says, I want this property of this object, you've got to return an AEDesk that's that value. That's all there is to it. Setter, the same thing. If somebody says, here's a token of an object, here's the property, and here's the new value I want you to set it to, you should set that property somewhere in your application and return error or no. That's all you need to do.
If there are other parameters on an Apple event, you have to extract them manually with good old AE Get Param Desk. And there's a useful more OSL routine to coerce it. If it happens to be an object specifier, it'll do the resolution for you. You know, this is useful for things like set name of file 1 to name of file 2. In that case, the 2 parameter is an object specifier, name of file 2.
And so what happens is the set command will resolve the direct parameter name of object 1 and give you a token. And then it'll be set token to object specifier. And then your setter will have to say, well, you know, I don't know what this is. It's an object specifier. So you hand it off to coerce object, MOSL coerce object desk. And it will take the 2 parameter, the object specifier.
And it will call back inside your application and resolve that and get the name of file 2. And return the string and say, okay, this is what you should set it to. Set name of, set this token to this string. And then you just do it because then you know what it is.
More OSL does deep object resolution, which is really cool. It's for cases like... First file of every folder of every disk. That's going to be a list. And what that's going to be a list of is on every disk, for every folder at the top level of every disk, you want the first file of each of those folders.
Or the name of the first file of each of those folders. And so, MoroSL has an object resolution algorithm that goes deep into those structures and creates one long list from that. We could go into it. It's well documented in the MoroSL documentation. So, without further ado, if we could have demo machine one up.
Great. When you download more OSL, one of the first things you notice is you get a lot of the more is better classes with you. You get more Apple events, more Windows, more text utils. The demo application relies on a bunch of those. But there are two main things. The more OSL files, relatively small. Most of the work is done in this 140k text file called moreOSL.c.
There are a couple of separate files for the string comparisons, which are A, heinous, and B, the cause of many bug reports, which Quinn has filed, basically saying AppleScript should either do this itself or have callbacks to do this, because as you might know, it's very hard for your application to do string comparisons the same way AppleScript does string comparisons, and that can be unexpected to users. So we're taking that feedback and trying to do something with it.
There's a a a small source code file to manipulate tokens, small source code file that's some helper routines, but really, moreOSL is itself a relatively small piece of code. The second thing you'll find is a fully functional test application in CodeWarrior called testmoreOSL, and the delightful thing about this is it has almost no user interface at all. So you won't be tempted to try to play with the UI. It is mostly a scriptable application. And so if we um go to CodeWarrior, and open up should have moreOSL, testmoreOSL.com.
Here's the test application, and there's one source file for the test application and a terminology resource, and that's about it. And then the rest are the more OSL sources. And the test application is one file, and what it does is it creates a model of a, you have the application, the application has windows, and each window can have nodes in it, and nodes are just simple rectangles, and nodes can have subnodes in them.
And that is the user model. So what we did was we created a user model, windows, nodes, subnodes. Then did a terminology for it and came up with all the appropriate four-character codes for all of the events and the class and things like that. And then built those tables and wrote those accessors like we talked about. So, for example, in the testmoreosl.c file, here's the, here are all of the routines on the application, because the application contains windows. There's a getter, setter, counter, and access by index routine, and those are listed in its appropriate tables.
There's the events it handles, open, reopen, open document, quit, and make. So basically you have one routine for the getter, setter, counter, and access, and then one routine for each event it takes. And the routines are pretty simple. So here's the application getter. I thought I'd set the text size on this. I hit power plant. There we go.
So here's your application getter, and it gets a token in, and if the token is a property, then you switch on that property, and if it's the version, then you get the version property and return it. If it's a unique ID, then you return a unique ID. And otherwise, you say, no such object. It's a very simple getter.
So then there's a window class, and the window has its getter setter, and its event it responds to, which is close. And so here's the handler for the window close event, which is basically-- handles the saving in parameter, so it gets that, and then closes the window with or without the save options. It's a very simple handler.
So here's the document inside the window, and the document handles make new. And so this routine handles the... make event when it determines we want to make a document. And it creates a new properties record. And this actually uses more of the more is better window classes in order to put the window up on the screen. But it basically creates the classes for that.
And then here are some of the custom objects. Here's the node. And here's your node access by index. Since more OSL doesn't have like an L list structure, it has to manage its own data structures itself. It doesn't have collection classes to fall back on. So it just basically has an index list of nodes, which are node pointers. And then it finds it by index. And if it doesn't find it, then it says it doesn't have an L list. of this type.
So you can see that once you set up the more OSL structure, actually implementing the Apple event and object model handling, it's very straightforward. For every event and these specific things you have to do for every class, you write one routine whose inputs are these and outputs are these, and then the rest of the stuff is just basically handled for you, and it makes it a lot simpler. Let me show you this in action. Is it running? We'll debug this.
and run it. And we can open up the script. One of the things I love about this is that it comes with a test script which has some particularly heinous cases in it. I mean it ba--it really ex--exercises most of the code paths with--within the test application and within MoreOSL itself. So, for example, here's a lot of things that you should be able to send from AppleScript to your application and have your application handle.
You know, set frontmost of window to true and make new document with properties and set position of node dis--set position of some element of some document to a list. Make a new window with properties. You know, these are a bunch of things that you should support in your code if you have classes that support--that support them.
And so we're going to open up the event log here, show the event results. Let's compile this against the application. There are a lot of test cases in here. It's a big script. And we're going to execute it against that application. And here are all the test cases we're going to run.
and we're going to run it before the Apple event times out. And you can see as they scroll by, here are a whole lot of object model events going to this application. There's getters, there's setters, there's create new, there's delete, there's move, there's duplicate. And that relatively simple application, okay, it doesn't do anything with nodes, it does very little with Windows, but that relatively simple application is handling all of these Apple events and handling them well. You can see some of the stuff we're doing here. Set the properties property of a window to a list of properties, which is nice.
Set names of windows, get names of startup disks, exist files. Get ID of Last window. Here, the sum key form, which is supposedly take a random element from this list of element, that's fully implemented. So if we can go back to the slide machine, and we'll wrap up here.
Some other nice things about it. The application and window classes are populated, so if you're using the other more is better window classes, then you can just get that functionality for free for things like normal application and window properties. And it knows about file objects, which are another thing that Quinn filed a lot of bugs on, is that sometimes AppleScript will send your application queries about system-wide objects, like the name of a file given an FS spec.
And it expects you to handle that, to be able to get the name of an FS spec and return it. And if you don't know that you're supposed to do that, and nobody tells you you're supposed to do that, it's a pain in the butt, and it will result in a user bug report. But. More OSL does that for you. Okay. Here's some notable omissions.
There's really no object management. Like I said, there are no collection classes for your objects, so you need to manage your objects yourself, and you need to have the actual routines to do make, new, delete, move, and duplicate the objects yourself. I mean, the routines will call you, but since it's not C++, it's not going to fire off constructors and destructors automatically. You have to write that code yourself. The print event is not implemented. Selection. And insertion points. Generally, the text classes are not supported.
You'll have to do that yourself. Like I said, form relative position isn't implemented, but we're going to try to twist somebody's arm to get that in. And no support for things like properties of a built-in type. Like if the result of a get operation is a string, and you ask for the class of that, if you ask for class of version of application, it won't handle that right. But then again, I don't know. I know of relatively few applications that do. And it also doesn't support class properties whose values are records or lists. And that's a philosophical decision, and we're going to have a philosophical discussion about it.
So, in summary, if you want to use PowerPlant, PowerPlant can provide easy scripting for your scriptable application. There are some things, you know, a lot of things you have to do yourself. But you can really get started by just overriding, you know, inheriting from, descending from LL object and overriding a few handlers, and you can get a scripting implementation started. What I really recommend you do is you trash the default dictionary implementation, and you start from the beginning rather than just trying to, like, monkey with the sample application.
For more OSL, if you don't have a framework-based application, this is really most appropriate for it. It's the fastest way to get the basics done, because all you do is write a few handlers, install them in tables, and there you go. It assumes you're doing your own object management, which most of you are doing anyway.
And it's very well commented and very well tested. And we have some investment in it, in that when we come out with a new dictate of how a new AppleScript feature is supposed to be implemented, like a properties property, we'll try to get that into more OSL as sample code.
We have a couple of sessions coming up in a very short period of time. I think it's at 3:30. A feedback forum across the hall, way on the other end, for general feedback on AppleScript. If you want to know more about doing scripting in Cocoa, you should go to the Cocoa in-depth session instead, which is across the street at the Civic Auditorium.
A long walk, too bad, but Mike Ferris will give you a good show on what you can do in Cocoa scripting. Same contact information for the last presentation, but for Code Warrior and PowerPlant, I recommend you contact Metrowerk's technical support. And for questions about more OSL, DTS at Apple.com. Thanks very much for your attention. Yes? Yes.