I’m working on a little floating note window application that one can use to make new documents in TextEdit (acutally it will send them to Mori Notebook, which uses exactly the same text handling as Textedit.)
The whole application is written using the Cocoa framework to handle the windows, prefs, and text preparation. Then when the note is ready to be made into a document, a button will activate a NSApplescript call that will contain some line similar to this:
set theText to " "
tell application “TextEdit”
make document with properties {text: theText}
end tell
Right now I have three questions (which themselves should demonstrate how incomplete my understanding is):
I was planning to use [NSApplescript initWithSource: theAssembledScriptAsString] where the source string is assembled basically “by hand”: theAssembledScriptAsString = @“set theText to "” + the user’s text + @" " \r tell application…end tell"
Is there an easier way to get the user’s text into the script, rather than insert it explicitly into a quoted string? For example, “set theText to theUserString” and somehow get theUserString to access the Cocoa object?
I would like to be able to include the clipboard contents as part of the user’s text portion. When Applescript Dictionary lists the type “text” what type of Cocoa object does that correspond to? NSString, NSAttributedString, NSTextStorage, etc? There might be graphics or tables, etc on the clipboard.
Can I avoid the NSAppleScript call altogether, and have a button directly activate an applescript, even though this isn’t an applescript studio app.
I’m sorry if I’m not explaning my dilemma clearly, but I appreciate any feedback/advice/questions. Your guidance has always been helpful.
I wouldn’t rush off to post to apple’s lists quite yet. This is a simple topic that you don’t need to go to great lengths to explore. Here are my responses to your 3 questions…
Initializing and then executing your nsapplescript is probably the best way to execute a simple AS command from a within obj-c. For much more complex tasks, you could build your scripts in different ways, run them through the shell, or read them from a file… but this just adds complexity to an easy task. If the extent of your script is similar in complexity to what you’ve posted above, then I’d stay with this method.
I posted an example in another thread a short while back which outlines exactly what you’re trying to do. Check out my last post in the thread. It works along the lines of what you’ve already posted. Pick apart the first line where I declare the ‘asString’ variable to find the line that contains the script. Note that where I insert “%@” is where a NSString (which is declared elsewhere in the obj-c) named ‘thePath’ is inserted. Likewise, you could declare your nsstring in your code as appropriate, and then use similar code to simply enter whatever that variable contains into the nsapplescript object. Assuming that it’s not going to contain tens of thousands of characters, this should be plenty sufficient for your needs.
NSpasteboards can, as you’ve suggested, contain a lot of different types of objects. But, by either requesting only the NSStringPboardType from the pasteboard when reading it, or by using the stringForType: method of the NSPasteboard class you can declare that only the string value of the pasteboard be returned and nil otherwise. You can (and should) programmatically make sure that only compatible types be taken from the clipboard and pasted into your text doc. When a dictionary refers to “text” it is probably either reference to an nsstring or unicode text. If it’s not clearly requesting unicode as it’s input, an nsstring should be sufficient. Applescript is a little less strict about what it accepts, trying to make sense of it automatically if the class doesn’t seem to match the data. If it doesn’t say explicitly in the dictionary what it’s expected class is, then you may have to find out by trial and error by stocking the pasteboard with generic, conflicting data and seeing what happens.
Interapplication communication is not widely available through many means, applescript being the most complete and flexible method. I can’t think of a better way to do what you’re doing, so I’d stay on the track you’re on. As I said above, I don’t see the value of reading an external file for the source of your script, unless you want to be able to edit the script regularly in the future. If the command you’re going to send is static and will unlikely change, then there should be no need to read a file into a string before using it to create an applescript object to execute. Just hard-code it and be done with it.
EDIT:
I just remembered something. In my librarySE plugin, I do something pretty similar to what you’re doing… creating a new document in script editor with a default tell block pre-inserted. To exactly imitate the behavior of apple’s standard library plugin, I had to do a little trickery with how I added my text to the newly created document. Rather than creating the document with a text property, I instead created an empty doc, and then inserted the text into it. It’s a bit kludgy, but it did the job and works well. Here’s the code that I used for that…
NSString *asString = [[NSString alloc] initWithFormat:@"tell application \"Script Editor\"\nset newDoc to (make new document)\nset text of newDoc to \"tell application \\\"%@\\\"\\n\\nend tell\"\nend tell",theKey];
NSAppleScript *asScript = [[NSAppleScript alloc] initWithSource];
[asScript executeAndReturnError:nil];
[asString release];
[asScript release];
Hope that helps,
j
That helps a lot. In fact, it helps enough that maybe I can more clearly write some of my questions.
thanks for the info about building the string with %@; it will make things a little simpler.
Actually, I wanted to bring along images and tables and whatever TextEdit could handle. I know that if I select a lot of different items on a webpage, TextEdit can handle most of it, converting it to rtf. I’d like to bring along all the info, types and formatting that I can.
However, I tried this:
tell application "TextEdit"
set test to the clipboard
make document with properties {text:test}
end tell
And I only get plain text entered into the new document.
I’ve seen some on these boards suggest using “Keystroke + v” type constructions to get rich text input, but I was hoping to avoid that kind of thing. Is there no way to send through something containing images and formatting?
I still meant that I would use AS for interapplication communication, but instead of using NSApplescript, I was wondering if I could put a full script inside the package and then wire up a button to activate it (through the IB Applescript inspector) even though I didn’t create the project as an applescript studio app. I tried to compare the two projects (the current cocoa app and the previous incarnation as a ASStudio app) and the only difference I found was the inclusion of the AS frameworks. So I added it, wired up the button to message the script, but still didn’t get any activity (I tested it with just a simple “Hello World” script.) Am I missing something here? I know that I could just us a NSApplescript and read it in from the package, but I was hoping to let the applescript dynamically take some info from one of the text storage objects.
For prewritten scripts, using NSAppleScript to compile and execute them is quite simple.
If you need to supply the script with arbitrary data then code generation is a crude technique, and it’s important you’re aware of the pitfalls; e.g. creating a literal string using stuff like ‘src = “hello "%s"” % name’ is all well and good until ‘name’ contains an unescaped backslash or double-quote and then you’ve got at best a fatal bug and at worst a glaring security hole. A far safer and more powerful solution is to assemble an Apple event (see NSAppleEventDescriptor) and pass that to your NSAppleScript instance’s executeAppleEvent:Error: method to invoke the appropriate handler in your script, passing in any relevant parameters.
There are a number of ways to do IPC on OS X (e.g. shared memory, unix pipes, Mach messages, sockets, Apple events, Distributed Objects) though talking to TextEdit will require Apple events. And there’s a variety of ways to send AEs: AppleScript is an obvious one, but you can also use the original Apple Event Manager API or a combination of NSAppleEventDescriptors (to build events) and AEM (to send them); somewhat verbose but quite doable. Various other languages have bindings available, e.g. Perl’s Mac::Glue and Python’s appscript.
Thanks everyone for the help. I could have spent weeks just to find out in the end it wasn’t possible to begin with.
Looks like I’ll probably end up making it a plugin of the original app (Mori Notebook) although since it does support services NSService might work.