Cocoa For Applescripters part 1: call method/rtm

Ok… we’ve all had that script that looks like it should work but fails miserably and think there’s gotta be a better way. Well there is! I have spent the last several weeks porting all of my Applescript Studio programs to ObjC and Cocoa, and let me say the following: I can’t read my code out loud and hear English anymore, but the code runs MUCH FASTER on my humble little G3 700 iBook (WITH topped up RAM), makes use of more powerfull features of the programming environment, and other things besides. I am not trying to convince you to do the same. This is just where I started the process and You too can take advantage of those powerfull features I mentioned without doing a total rewrite. Here’s how: in the Application Suite of ASK there’s a command call method: here’s what the entry looks like (kinda)

This is what is sometimes referred to as the ObjC bridge. It allows you, the applescripter to call straight to the ObjC runtime, as if you were actually programming in Objc. (Which btw is the native language of Cocoa, the framework upon which all your applescript studio goodness resides). So now, you’ve seen the dictionary entry, but like most dictionary entries, it gives a definition, without any hints on how to use it, or why it might be usefull. lets start by cleaning up that dictionary entry a little and make it a little more encyclopedic:

there are six possible call method syntaxes (syntices? syntaxae? syntaxim?) I mean variants:


call method methodName of theObject
call method methodName of class objcClass
call method methodName of theObject with parameter x
call method methodName of class objcClass with parameter x
call method methodName of theObject with parameters {x,y}
call method methodName of class objcClass with parameters {x,y}

So the first part is figuring out the reciever. If you want to perform an objc call you probably already know what object it is that you want to call. Let’s say you have a webview that you want to display html which you’ve generated in your script, this is an ability outside the realm of applescript studio, but within objc’s reach. Let’s look up the documentation for webkit to see what we can see: file:///Developer/Documentation/Cocoa/Conceptual/DisplayWebContent/index.html Take a look at the page on “Core Web Kit Classes” as we want to know what all the parts are and how they fit together.
A web view is a controller that manages web frame views (that render and display the HTML and web frames, which interface with the various data classes for making the HTML data available to the view. The web view itself has access to all of it’s sub-webframes through it’s “mainFrame”. Let’s see what’s in the WebFrame class (link is in the left hand panel) All apple class documentation follows the layout in this page, so It’s good to know what they look like so you can find the method you want. Go down to the section labelled “Method Types” as this is the quick index of all the methods grouped by type. Under loading content we see “-loadHTMLString:BaseURL:” this looks like what we want. (note on method names: those that start with “-” we call on objects, whereas ones starting with “+” we call on the class. Also note other methods that the class can do are found by following the Inherits From and Implements links near the top of the page. Further, where ever a colon, “:”, appears an argument is expected.) Yup here’s the call we want to make… now how to do it.

The objc call looks like

 [[webView mainFrame] loadHTMLString:thePage baseURL:@""]; 

translating the syntax into applescript we get

 tell webView's main frame to load HTML string thePage base URL "" 

(that base URL argument is for determining where relative links in the html point to… but you don’t need to worry about that for this tutorial). As there is no applescript studio handlers for WebKit right now, you can’t do that. We have to use the ObjC bridge:


set theWebFrame to call method "mainFrame" of theWebView
call method "loadHTMLString:baseURL:" of theWebFrame with parameters {thePage,""}

Some final notes. Although the objc name has a “-” or “+” at the beginning, don’t include it in the call, also leave out the types and arguments, but the colons are part of the name. An applescript string gets tranformed into an NSString without you having to worry about it. Most things Applescript studio can work with have similar class names in objc, with the parts squished together, using camelback capitalization and starting with “NS”. menu → NSMenu, menu item → NSMenuItem. That should help you look up the documentation. Don’t forget to add the webkit framework to your application if you want to run this.

This has been part one, hoped you like it… tune in next time for part two, where we’ll look at using interface builder for objc through ASSer’s eyes.

I forgot the second part of this post… I first wanted to cover calling objc, and also where to find the information you need in the documentation. I did a little teensy bit of a guided tour to find webkit docs but here’s where you’ll documentation on the common classes you’ll come across:

file:///Developer/Documentation/Cocoa/Reference/*/ObjC_classic/index.html

note where the * is choose between ApplicationKit and Foundation (note webkit should also be present)… appkit has all the visual classes, foundation has everything else (notably, NSApplication, NSBundle, and NSDictionary). Don’t forget that classes inherit methods, (like scripts with parent properties) so if you don’t see the method you want to call try following the inheritance chain links, as well as the protocol conformance links. Generally this documentation is very complete and well cross referencing. An absorbing read.

Alternatively, you can use the GPL “Cocoa Browser” application, which is another interface to the same documentation. As for web resources, let me reccomend CocoaDev (www.cocoadev.com) where you can post questions and search out much information about all things cocoa/objc related.

Thank goodness someone finally addressed this issue. I have been wondering how to convert my AS code over for the reasons posted. I hope it is as simple as you claim!

Thanks,

John

Thank you for you explanation. I followed your instructions and decided to experiment in xCode for learning purposes.

I started a new ApplescriptStudio project.

It contains 1 window.

I want to call the method “kCGDesktopWindowLevel” which is supposed to place the window at or just above the desktop level.

I get no errors with the following code but the window stays frontmost, not moving to the desktop.

Can you help?

on awake from nib theObject
set TheMethod to “setLevel:”
set theValue to “kCGDesktopWindowLevel”
call method TheMethod of theObject with parameter theValue
end awake from nib

Part of the problem (I think) is that kCGDesktopWindowLevel is not a string but that is what you are passing to the method. You can take a look at this project that shows how to make it work by calling a new custom method:

http://homepage.mac.com/jonn8/as/dist/DesktopWindow.sit

Jon

Jon,

Thank you for your example. I wish I had enough knowledge to figure out the code you have provided in the example. I would never have come up with that on my own.

The example works perfectly.

Nikki

… there should be an Enumeration, but here’s the gist. For most constants that have symbolic names you need to look up the value yourself. make a new project (Cocoa Application) edit main.m, to the following


#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
	NSLog(@"%d",kCGDesktopWindowLevel);
    return 0;
}

build and run it and you get:

so the value you want to set is -2147483627 as in


call method "setLevel:" of theWindow with parameter -2147483627

don’t worry about figuring that part out by yourself for now… this is part one remember. You just asked an interesting question… PS Jon’s answer is cleaner than mine… But learning to write that stuff will be in part 3.

Thanks for the bit of code for finding corresponding numbers for constants!

That will be useful for finding out many others.

With much appreciation,
Nikki

I have a table in my app that I would like to print in a horizontal orientation. However, AS provides no way to ajust orientation, so I looked up printing in the reference you indicated in Part 1. It led me to the NSPrintInfo Class (or Object - I don’t know what to call it) which offers:

SetOrientation:NSLandscapeOrientation

which seems to be the right one. So I added:

call method “SetOrientation:NSLandscapeOrientation”

before the print command for the table. However, it still printed with portrait orientation. I then wondered if I was missing an object at the end of the method, and scratched my head, unsure of what object to call. So I tried:

call method "SetOrientation:NSLandscapeOrientation of object “NSPrintInfo”

with no effect. Then I tried setting the table to theTable and

call method "SetOrientation:NSLandscapeOrientation of object theTable

and this had no effect, either. I am unclear on what object to call, and moreover, why none of these methods work. What am I missing here?

Thanks,

John

NSLandscapeOrientation is a constant symbolic name… not a value, to find it’s value to use as a parameter, look at the code three posts up (?) to find out that it is 1

“setOrientation:” is the name of the method. The reciever needs to be an object of NSPrintInfo type, and there isn’t one in AS just hanging around, so we need to ask the class for it first.


set printInfo to call method "sharedPrintInfo" of class "NSPrintInfo"
call method "setOrientation:" of printInfo with parameter 1

It all makes sense except for one element:

In the printInfo example, how did you know to call “sharedPrintInfo” to get the object? And in the WebView example, how did you know to call “mainFrame”? I don’t even see “mainFrame” in the documentation you referenced.

Thanks,

John

I subscribe to the last question… Browsing the docs, and being explained, it seems as easy as appending a “whatever:forever:” to a “call method” command, but then when you try it, it’s another business.
I think simple applescripters, as me, need some adjustments in our brains before going ahead with obj-c bridge. :rolleyes:
Thanks for your help on these topics, mooresan!

Alright… you asked for it, more tips to reading the documentation. (by this I mean the stuff linked from /Developer/Documentation/Cocoa/Reference/*/ObjC_classic/index.html) On the index pages, you’ll see a large listing of classes. You should be able to find one relevant to what you want to do. GUI applications and really user oriented classes are in the ApplicationKit. In the relevant class there’s some things to look for. First (using the NSPrintInfo example) you’ll see a class description (right under the inheritance/conformance listing) This one mentions that “A shared NSPrintInfo object [ or instance ] is automatically created for an application and is used by default for all printing jobs for that application.” By this we know that there’s only one, and that we can access it via the classes “sharedPrintInfo” method. Other classes with shared instances accessed in similar ways include: NSApplication, NSBundle, NSOpenPanel, as well as the other standard panels. As a class is to a cookie cutter, an instance (or object) is to a cookie. These classes have only a single cookie that everyone has to share that is actually in the cutter. :slight_smile:

When gazing through the method listings a little further down we see some are marked with a “-” at the start, and some with “+”. The ones with “-” are “instance methods” (ones that get called on an instance) the ones with a “+” are “class methods” that get called on the class. This distinction is very important.

Another thing that is important is that although the documentation for the objc methods interupts it’s name occasionally with arguments and types, these aren’t part of the name, and we can’t call a method in applescript using the normal interupted style syntax. For example, if a method is written as: (taken from NSWindow)

The name is actually that bit at the top ie “dragImage:at:offset:event:pasteboard:source:slideBack:” note no “-” or “+”, and we pass a parameter list with items in the order mentioned in the list ie {anImage, aPoint, initialOffset, theEvent, pboard, sourceObject, slideBack}. Assuming those variables are defined properly and our window INSTANCE (note the “-”) is theWindow we get


call method "dragImage:at:offset:event:pasteboard:source:slideBack:" of theWindow with parameters {anImage, aPoint, initialOffset, theEvent, pboard, sourceObject, slideBack}

This isn’t a particularily useful method, but it serves to illustrate what parts are the arguments, and what the name of the method is.

To call a method on a class for example (NSBundle is in Foundation) +mainBundle of NSBundle, we do


call method "mainBundle" of class "NSBundle"

Please feel free to ask more questions… (By the way, part two is written, as is part three and part of part 4. Part two, IB for Objc for ASists should be posted on Friday evening JST, so those in Europe/NA should be able to read it Friday during the day… that is if I don’t go out somewhere…)

I still am a bit confused, so here you go:

I still don’t understand how you know WHICH method to call to create an object of proper type. In your last example you used:

 call method "mainBundle" of class "NSBundle"

I don’t see how you know to use “mainBundle” and not “allBundles” or “bundleWithPath” or “bundleForClass”, etc. In the NSPrintInfo example you drew attention to the documentation under “Description”, which made sense. However, I don’t see the same clear definition in the NSBundle documentation.

Could you walk me through the following?:

I want to use a call method to return the file name of a document that was just saved by the user (ASS will not do this properly). Assuming I go to the NSDocument class, I find a definite possibility:

  • (NSString *)fileNameFromRunningSavePanelForSaveOperation:(NSSaveOperationType)saveOperation

However, I see that:

This method has been deprecated. Instead use saveDocumentWithDelegate:didSaveSelector:contextInfo:

This method is described as:

  • (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo

So, now, looking at this, I am lost as to what to do with it. For example, what would the delegate be (and in what form would the parameter be as id?) . . . in what form does the parameter SEL have to be and how do I know what value to give it? . . . what parameter would match (void) for contextInfo? . . . and what is contextInfo referring to?

Perhaps I am looking at the wrong method, but since the first one has been “deprecated,” I don’t know what else to use. . .

Thanks for all the greatly needed help,

John

I am going to start by saying that I am not an expert at any of this… especially the document based architecture. That said, I am willing to try. The document architecture in Cocoa has many component classes. NSDocumentController, NSDocument, and NSWindowController (and maybe others) work together to do most of the work. A document controller manages all the documents, file types, and loading/saving. A document holds the information for one document. A window controller interacts with the window on behalf of the document. I believe if you wanted to find out thefile name of the last document that was saved, you’d want to look at NSDocumentController, which knows about all the documents and knows about saving/loading. A document only knows about it’s own data, and maybe a window controller (I think). In the NSDocumentController class in the Class Description section we see the following

This sounds like what we’re looking for… we need to get the shared instance and then ask it about the last saved document.

Under “Method Types” the first one is “+sharedDocumentController” which solves the first part. The second part is slightly more difficult… there’s no “-lastSavedDocument” method… hmmm… how about the first item of “-recentDocumentURLs”… this returns an array of URLs (which are strings), so it’s not exactly the filename… but it may suffice… And I don’t know if an NSArray can be used directly in AS like an applescript array. I also don’t know the sort order of this array…

Assuming the easiest code to get the recent URL (the first one is most recent and you can use an NSArray as an applescript array)


set docCtrlr to call method "sharedDocumentController" of class "NSDocumentController"
set urlArray to call method "recentDocumentURLs" of docCtrlr
set fileUrl to item 1 of urlArray

Assuming the more difficult (it’s the last item and you can’t manipulate the array directly)


set docCtrlr to call method "sharedDocumentController" of class "NSDocumentController"
set urlArray to call method "recentDocumentURLs" of docCtrlr
set fileUrl to call method "lastObject" of urlArray

Even more work:


set docCtrlr to call method "sharedDocumentController" of class "NSDocumentController"
set urlArray to call method "recentDocumentURLs" of docCtrlr
set fileUrl to call method "objectAtIndex:" of urlArray with parameter 0

Like I said though… this is out of my expertise.

The method “recentDocumentURLs” returned nothing, triggering a “The variable urlArray is undefined” error – but thanks for taking a stab at it!

More importantly, I need to grasp the bigger picture so I can start using this throughout my application. I’ll throw this your way and we can move on to part 2. What I need to know is:

call method “mainBundle” of class “NSBundle”

I don’t see how you know to use “mainBundle” and not “allBundles” or “bundleWithPath” or “bundleForClass”, etc. In the NSPrintInfo and NSDocumentController examples you drew attention to the documentation under “Description”, which made sense. However, I don’t see the same clear definition in the NSBundle documentation.

And when reading the arguments and types for a method, how do you figure out what form the parameters need to take (Here’s a good criptic example):

  • (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo

Where do I find out what (id) refers to . . . the same goes for delegate, (SEL), didSaveSelector, and contextInfo.

Thanks,

John

Actually I thought to mainBundle because that’s what you call in AS to get the main bundle… (although in Cocoa it’s part of NSBundle, not NSApplication…)

  • (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo

Well I’m not sure if this is the method you want to use… but if you think so here’s some of the parts (there’ll be a better description of this in Cocoa for ASists pt 3) the “-” says it needs to be called on an instance (a “+” would mean a class). All the things in parantheses are types. The first one is what the method returns, in this case void ie. nothing. Next it’s divided up as a repeating set of label:(type)variableName. A variable of type id can be anything, and in this case it needs to support the delegate methods of the class (also in the docs, also in part 3 or 4). a SEL is another way to say method name, but not the string of the name… the name. I know of no way to create a SEL in AS. a void* is also like id in that it can be anything, but even looser, because an id is an Objc object, but void* could be a C type, or C++ virtual function pointer, or Pascal library code… ANYTHING. I don’t think this is the method you want…

I’ll begin by saying I don’t have a clue about any of this and can’t seem to find one…but really wish that I could. 8) I have many questions but will wait on most of them for your upcoming installments to see if they’re answered there. I just wanted to comment on your first two posts, so I could perhaps lend a TOTALLY inexperienced person’s perspective. This is all, of course, an attempt to steer your content towards providing me with exactly what I need to get started using the call method on my own.

There are a couple common problems I’ve seen with all of the obj-c/ call method tutorials I’ve found. The first, is that they are written from a full understanding of the system already, which predisposes the author to skipping over things that are important to the newbie. Other people have already asked some of my questions like: How do I know what something like “(id)sender” means, and should I and how do I provide it?.. How do I successfuly get the results of the call method?.. How do I create references to or instances of my AS objects and then send them appropriately to a call method? (i.e. you previously said "Assuming those variables are defined properly and our window INSTANCE (note the “-”) is theWindow we get "). I seem to always have some bit of information missing, and don’t know where to find it or how to say it.

The second, is that they often jump right into complex actions that have so many parameters and finite requirements, that their potential for error is too great to make for a good basic learning environment. I know you (mooresan) said that ‘this is out of your expertise’ a few times, but perhaps a few EASY examples should really be presented first, rather than some that aren’t “particularily useful”. Simple things like getting the title of a window, or finding the name of the app might be a better starting place than big complex methods that I won’t likely use. Don’t get me wrong here, any bit of help is welcome, as this topic has a SEVERE lack of documentation, but I feel that I would learn better from the bottom up, than by starting in the middle or near the top. Perhaps some of the more experienced obj-c guys could furnish some simple calls with instructions and details about what EVERY part is and how they arrived at the finished call syntax, because I still don’t understand the full process. There are many bit’s of code out there, but they don’t come with instructions on how they were extracted from the obj-c docs and coerced into call methods, which is really the important part for us inexperienced minds to be able to understand this fully.

This is what I mean about a simple example… Say I want to get the first responder of the current window. Programatically, it should be just accessing a value stored somewhere identifying which item is the responder for the window, and then spitting it out into an AS variable. The docs in the NSWindow info say I need…

Wow, this sounds easy. I instinctively write something like…

set theFirstResponder to (call method "firstResponder" of class "NSResponder")
-- OR --
set theFirstResponder to (call method "firstResponder" of class "NSWindow")

But (probably obviously, to you) neither works. So how would I go about this? It seems to me that I should reference the window, somehow. Is the firstResponder part of the NS responder or NSWindow class (or both)? Which one should I call of?

I hope that the next parts of your tutorial fill in some of the gaps for me, as I still cannot figure this out. Please consider writing some REALLY BASIC examples with detailed instructions on how and why you did things a certain way. There is plenty of jargon and encyclopedic information out there as far as what actions and objects and classes are, so please try to show us through example HOW things work, rather than just telling us that they do. Take basic things like window properties, document or data, pasteboard etc. and show us how to do them with obj-c rather than AS.

There are many threads (some still unresolved) here in this forum that could use or MUST use obj-c to be possible. I have been trying to work out methods of doing them on my own, but I am just not smart enough…yet. :cry: Some that come to mind are…
“Pasting text at insertion point”:
…use a call method to perhaps set the contents of the pasteboard (easily done in AS), but more importantly, invoke the NSText ‘paste:’ method to insert text wherever the cursor is. This can be done by connecting it to the file’s owner, but does not leave room for doing anything else. If you could do it with a call method, you could also then perform other methods or AS code.

“get the active text fied of a window”:
…only obj-c has the ability to ‘get’ what the current responder is. ASS currently does not support it.

“get the screen resolution.”
Obj-c should be able to find out the width and height of the current display, so you could mathematically set windows relative to the current display size in ASS. I realize there have been other “ways” of doing this, but they all eventually revert to scripting/reading system events or using external apps. We’re then just telling the other apps to do what we should be doing ourselves in OUR apps.

As I said, these are simple examples, but they would provide a good point for someone like me to get my feet wet with obj-c. If documented thoroughly, the process you use as an experienced programmer to figure these easy things out, could be invaluable to someone who can’t even get something so simple. It would show us the process of figuring what goes where, rather than having you tell us it’s all there and eventually have to find it all ourselves anyways.

Thank you for your time and interest in sharing this with us. It is not my intent or desire to offend, but rather to express that your hard work may be in vain if no one is able to excel having studied and tried your examples, and then still failed. I look forward to the upcoming parts to this tutorial, as they are slowly unlocking the secrets I greatly wish to know.

Keep up your hard work, we’ll get it eventually… :rolleyes:
j

You’re making it more difficult than it needs to be. You see that this is a method of the NSWindow class but what you don’t get is that that class is already instantiated in your application when you open any window. The “call method” command is an AppleScript command that translates the AppleScript terminology to the Objective-C terminology for you so all you have to do plug in the AppleScript objects when you’re calling the method:

Further, if you wanted to set the first responder, try this:

You see that colon? That means it needs a parameter which we’ll pass as the text filed (or any other element you wish). The firstResponder method was simply returning data from the window so it didn’t need any other parameter and therefore didn’t need the colon.

Jon


[This script was automatically tagged for color coded syntax by Convert Script to Markup Code]

I am sorry I didn’t post part three yet… and you’re right… I don’t want this to be in vain… so keep the questions coming. I don’t have time to respond to your whole post before I go to work, but I’ll start with that firstResponder bit. You did good to find this in the docs… that’s the first major step, being able to navigate the class documentation. As you learn a little bit more of objective-c you’ll come to see that this documentation is really superb, and you couldn’t ask for a better reference manual. (Which is why there are few books that are just straight references for Cocoa, while using Cocoa books abound)…

And you also chose several good examples, of easy calls into obj-c, I initially wanted something that you can’t do in ASS but I didn’t know a good example that is simple and can’t be done in ASS. Anways on to the response: You looked in the NSWindow documents, so the class of objects (instances) that respond to the method are NSWindow objects. the method name was given as -(NSResponder*)firstResponder, let’s disect it. starts with “-” or “+”, in this case “-” means we use one of the “call method theMethod of theObject” forms (as opposed to the “of class” forms). Immediately after the ± is an optional return type. In this case (NSResponder*) means that the result of the call method is going to be an NSResponder (which corresponds to the responder class in the ASS Application Suite (I think that’s the right suite)). Next comes a repeating set of “label colon type argument” where everything but the label is optional depending on the method. In this case the method expects no argument (parameter) so we use just “call method theMethod of theObject”. To get the method name we get rid of everything but the labels and the colons and squish it together, in this case “firstResponder”, now it needs to be called on an instance… hmmm well, in this case which window’s first responder are you tring to get? you could use any window in your app, but it’s probably either the main window or key window. If its the main window we get:

call method "firstResponder" of the main window

And now I should go…