How would I retrieve an array of paths to the selected files in Finder?
I have searched around but have only found links regarding AppleScript. I have also looked at NSWorkspace and NSFileManager but I didn't find anything.
Expanding on #Bavarious's (correct) answer, here's how I've gotten the selection from Finder using the Scripting Bridge:
#import "Finder.h" //my copy is here: https://github.com/davedelong/BetterInfo/blob/master/Finder.h
FinderApplication * finder = [SBApplication applicationWithBundleIdentifier:#"com.apple.finder"];
SBElementArray * selection = [[finder selection] get];
NSArray * items = [selection arrayByApplyingSelector:#selector(URL)];
for (NSString * item in items) {
NSURL * url = [NSURL URLWithString:item];
NSLog(#"selected item url: %#", url);
}
If it is possible to get the list of selected files in a given Finder window using AppleScript, you can probably use Scripting Bridge in a Cocoa application to interface with Finder. Quoting Apple’s documentation,
Scripting Bridge is a framework and a technology that makes it much easier for Cocoa developers to control and communicate with scriptable applications. Instead of incorporating AppleScript scripts in your application or dealing with the complexities of sending and handling Apple events, you can simply send Objective-C messages to an object that represents an application with a scripting interface. Your Cocoa application can do anything an AppleScript script can, but it does so in Objective-C code that is integrated with the rest of your project’s code.
There is no Cocoa class that represents Finder or, more specifically, Finder windows. Finder is an application, and a scriptable application at that, so Scripting Bridge is the way to go.
Related
I'd like to programmatically set the Finder label (a.k.a. the color of items in the Finder) from within Cocoa. I can read the Finder label of a URL u without problems using
MDItemRef itemRef = MDItemCreateWithURL(NULL, (CFURLRef)u);
CFStringRef s = MDItemCopyAttribute(itemRef, kMDItemFSLabel);
label = [(NSString *)s intValue];
This API does not provide any mechanism for writing the metadata, so my guess is that the solution lies elsewhere entirely. Any hints?
Edit: I know this is possible using AppleScript, but this is disallowed under sandboxing. I need a pure in-process solution.
For Snow Leopard and above, you can utilize the URL Resource API, as seen in this answer.
I have an AppleScript that I am trying to convert to ScriptingBridge. Since my application is a C++/Obj-C application, ScriptingBridge is much easier to use and quite a bit faster (not to mention I hate dynamically building AppleScripts).
The AppleScript sends a message to Photoshop to open a file. The file parameter is sent as an alias, but ScriptingBridge imports the parameter as an id. I don't know what Obj-C object I should pass in?
I've tried passing an NSURL and an NSString (probably incorrectly :-P), but to no avail. Any suggestions on what I should be passing for the file alias?
The short answer is that you can't open documents in Photoshop with Scripting Bridge.
Apple's docs really spell it out like it is. All classes must have a container, which is a mutable array, that they need to be added to before they can be acted upon, as shown in the generated header...
#interface photoshopCS4Application : SBApplication
- (SBElementArray *) documents;
- (SBElementArray *) fonts;
- (SBElementArray *) notifiers;
... and that is the complete list of top-level containers available to us. The open command requires a photoshopCS4OpenOptions to be generated and populated. Because the API doesn't expose the array to store the newly created photoshopCS4OpenOptions object, we can't use a newly created photoshopCS4OpenOptions object. Therefore we can't make a target document and by extensions can't use the open command in Scripting Bridge. The same can be said of all the commands that require some kind of options object.
The only workaround that I have sorted out is to either open a document with native Applescript called from Cocoa or objc-appscript, and then parse the documents array looking for the one just opened. It's not ideal, but then neither is Scripting Bridge because it requires application developers write their scripting APIs in a very specific way that is not native to the OSA framework.
If your program is such that opening a Photoshop document can be executed outside your AppleScript script/Scripting Bridge code, Cocoa provides a method to open files with a specific application:
[[NSWorkspace sharedWorkspace] openFile:#"/Users/bavarious/Desktop/test.psd" withApplication:#"Adobe Photoshop CS4"];
or, if you want to use the default application that handles that file type, you can drop the application name altogether:
[[NSWorkspace sharedWorkspace] openFile:#"/Users/bavarious/Desktop/test.psd"];
Consider Appscript. http://appscript.sourceforge.net/
Here's the code using that:
APApplication *adobePhotoshopCs4 = [APApplication applicationWithName: #"Adobe Photoshop CS4"];
id result = [[adobePhotoshopCs4 open_] send];
(Note, I'm not a Cocoa programmer - I mainly use Appscript with Python but Appscript comes with ASTranslate which translates Applescript into Python, Ruby or Obj-C and that's the output - but I've found there are subtle mistakes in the past sometimes with the translator)
I have an array of filenames that i would like to pass to an external application for opening. I'd like to do one of the following:
a) Somehow instruct OSX to open all these files with an associated application, but it must invoke the target app's openFiles NSApplication delegate method
b) Specify the application to open these files with (and also invoke openFiles)
Basically it doesn't matter which solution to realise, because these files will be associated with the target application anyway. How would i do one of these things?
To open a whole bunch of files at once, send the shared NSWorkspace object an openURLs:withAppBundleIdentifier:options:additionalEventParamDescriptor:launchIdentifiers: message, or call the LSOpenURLsWithRole function or the LSOpenFromURLSpec function. Either way, you'll pass an array of URLs to items to open.
Each one of these will let you identify a specific application to use. NSWorkspace lets you specify it by bundle identifier, while the two Launch Services functions let you provide the URL or FSRef to a specific application bundle.
… it must invoke the target app's openFiles NSApplication delegate method
That isn't possible to require, because (a) the application may be document-based, in which case it probably does not have an NSApplication delegate and, even if it does, such a delegate would probably not respond to application:openFiles:, and (b) the application may not be Cocoa-based, in which case it would handle the Open Documents Apple Event directly. None of this is your application's business, so don't worry about it.
First add your video files to Resources Folder.The code like follow:
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
NSString* videoPath = [bundlePath stringByAppendingPathComponent:#"/Contents/Resources/video.mov"];
[[NSWorkspace sharedWorkspace] openFile:videoPath];
I want to use Scripting Bridge to add a movie to iTunes. And preferably letting me choose between a 'music video' and a 'movie'. I know both Objective-C and AppleScript so I thought it wouldn't be that hard but I can't figure it out. I know how I would use NSAppleScript for it but I'm targeting 10.5 or later and read that Scripting Bridge obsoletes NSAppleScript. Is that right?
All I got is
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier: #"com.apple.iTunes"];
Which is, as you can see, not much at all.
Step 1. Generate iTunes.h header file:
sdef /Applications/iTunes.app | sdp -fh --basename "iTunes"
Step 2. The code to add a media file looks like the following:
NSString* sourceMediaFile = ...;
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
iTunesTrack * track = [iTunes add:[NSArray arrayWithObject:[NSURL fileURLWithPath:sourceMediaFile]] to:nil];
NSLog(#"Added %# to track: %#",sourceMediaFile,track);
You should use the "scripting definition processor" (sdp) program to generate a header file from iTunes' scripting definition (.sdef) file (which you can get using the sdef program):
sdef /Applications/iTunes.app | sdp -fh --basename "iTunes"
This'll give you a file called iTunes.h. Then you include that header into your project and read through it to see what the iTunes scripting bridge interface offers.
If it seems like you won't be able to do this with the scripting bridge (it's possible -- not everything that can be done via an app's AppleScript interface can also be done via the scripting bridge), just go ahead and write an AppleScript to do it instead, and then execute that in your program with NSAppleScript.
For the second parameter, it takes a playlist object (or nil as previously mentioned). Once you have fetched an instance of a iTunesPlaylist* object through some means (there are several depending on your needs), you can pass it as the second parameter.
Cocoa used to work on CS3 with the trick of putting a Cocoa bundle inside the main Carbon plugin bundle, loading it from Carbon and issuing a NSApplicationLoad(). That's because Photoshop CS3 was Carbon-only and used to unload the plugin bundles.
Photoshop CS4 uses Cocoa and has its own NSAutorelease pool in place on the main thread.
On Photoshop CS4 very simple window-based xibs/nibs loaded by a NSWindowController work out of the box.
But just add a binding to a control on the window and you'll get funny crashes, optionally when you close the window, or the second time you use the plugin, or even when closing Photoshop itself.
Why everything seem to work well until I use some advanced Cocoa features? I'm stuck.
EDIT: I've really found myself the solution to the broader problem "How to use Cocoa in a Photoshop CS3/CS4 plugin?". See below.
You have to create a new Loadable Bundle target that contains your nibs and your Cocoa code. Add the bundle product to the Copy Bundle Resources phase of your plugin. Then the code for a filter plugin that loads a Cocoa window with some controls would be:
Boolean DoUI (void) {
// Create the CF Cocoa bundle
CFBundleRef pluginBundle;
CFURLRef cocoaBundleURL;
pluginBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.example.plugin"));
cocoaBundleURL = CFBundleCopyResourceURL(pluginBundle,
CFSTR("Cocoa_bundle"),
CFSTR("bundle"),
NULL);
CFBundleRef cocoaBundleRef;
cocoaBundleRef = CFBundleCreate(kCFAllocatorDefault, cocoaBundleURL);
CFRelease(cocoaBundleURL);
// start Cocoa (for CS3)
NSApplicationLoad();
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// load the cocoa bundle by identifier
NSBundle* cocoaBundle;
cocoaBundle = [NSBundle bundleWithIdentifier:#"com.example.plugin.cocoa"];
// load the window controller from the bundle
Class testControllerClass;
testControllerClass = [cocoaBundle classNamed:#"MyWindowController"];
MyWindowController* winController = [[testControllerClass alloc] init];
[NSApp runModalForWindow:[winController window]];
[[winController window] performClose:nil];
[winController release];
// release the bundle
CFRelease(cocoaBundleRef);
[pool release];
return 1;
}
This is based on the Craig Hockenberry bundle trick. I'm still testing it but it should work both on CS3 and CS4.
I just started working on writing a Cocoa-based plugin for CS4. Really, there is almost no information out there on this topic, and I've been figuring it out as I go.
Start from this Apple example, and make sure you download the whole project, because there are a few little details missing from the text:
Carbon/Cocoa
Pick one of the Photoshop SDK examples (I used ColorMunger), and keep it simple to start, so just try to replace the "About" dialog box, using the Apple example as your template. Once you have that working with no memory issues, you should be on your way.
I've been a Java and Ruby programmer for 10 years, so my C/C++ foo is rusty, and I'm just learning Objective C as I go. Two "gotchas" I ran into, just in case....
do NOT create a controller object in your NIB/XIB file. Because, based on that Apple example, the controller opens up the NIB file in it's init method, and you get a really interesting recursive loop
The Apple example is embedding the Cocoa stuff in a Carbon based C app. The Adobe examples are all C++. Don't forget your extern "C" {} in your header file.
CS2 will load PowerPC Mach-O code as readily as CS3/CS4. Has anyone tested this Cocoa approach in CS2?
Currently I use Carbon for CS2/CS3/CS4 as this is guaranteed to work everywhere the plugin loads; and Cocoa for CS5 of course, whether 32 or 64 bit.
Chris Cox isn't optimistic about Cocoa working in anything other than CS5:
http://forums.adobe.com/message/3256555#3256555
So what's the real deal here? It's pretty hard to ignore advice from the horse's mouth.