Setting the Finder label from Cocoa - objective-c

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.

Related

Filter [NSWorkspace runningApplications] to contain only user applications (no daemons/UIAgents)

Is there a way to filter the list of applications given by [NSWorkspace runningApplications] to hide all daemons, etc short of manually checking each application's plist for the UIAgent key? If an application doesn't show in the dock, I'm not interested in it.
UPDATE: If you’re using Objective-C, my original answer below still applies, but if you’re using Swift this can very easily be performed more cleanly, thanks to Swift’s filter function for collection types.
let workspace = NSWorkspace.sharedWorkspace()
let apps = workspace.runningApplications.filter { (app) -> Bool in
return app.activationPolicy == .Regular
};
In Objective-C something similar can be done with NSArray’s various predicate-based and enumeration methods, but they’ll be a little more long-winded than their Swift counterpart.
I found the answer after some searching, but it's something that might not be immediately obvious.
An easy way to only grab processes which have icons in the Dock is by doing a simple fast enumeration loop and checking each NSRunningApplication's activationPolicy, like so:
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
NSArray *apps = [workspace runningApplications];
for (NSRunningApplication *a in apps) {
if (a.activationPolicy == NSApplicationActivationPolicyRegular) {
// Do stuff here
}
}
Typically, applications with normal windows and dock icons use NSApplicationActivationPolicyRegular. Menu extras and Alfred-type applications use NSApplicationActivationPolicyAccessory. Daemons, etc with no user visibility whatsoever use NSApplicationActivationPolicyProhibited. These constants correspond with the LSUIElement and LSBackgroundOnly keys in each application's Info.plist.
This approach should catch applications which have settings that allow the user to toggle the presence of the application's dock icon through setting their activationPolicy dynamically.

Printing without an NSView

Currently I'm writing an app for OSX which will eventually need to be ported to iOS.
The data that needs to be printed is being drawn via CoreGraphics into a PDF context - that is working perfectly.
I've been reading the Apple dev documentation on printing in both iOS and OSX, and, ironically, it actually seems printing from iOS will be easier.
On iOS, UIPrintInteractionController's printingItem property can take an NSData object containing PDF data and print that. Looks like it should be fairly straight-forward.
OSX on the other hand, (looks like it) requires using the NSPrintOperation class - but it seems the only way to get data into an instance is via an NSView. (+printOperationWithView: or +printOperationWithView:printInfo:).
Seeing as the content is formatted and paginated already it seems rather pointless to have to re-draw the PDF data to something like an NSView.
Could there possibly be another way of achieving this that I've missed?
This code is by no means complete, but for anyone who comes across this later, this is basically how you can print directly from an NSData stream:
#define kMimeType #"application/pdf"
#define kPaperType #"A4"
- (void)printData:(NSData *)incomingPrintData {
CFArrayRef printerList; //will soon be an array of PMPrinter objects
PMServerCreatePrinterList(kPMServerLocal, &printerList);
PMPrinter myPrinter;
//iterate over printerList and determine which one you want, assign to myPrinter
PMPrintSession printSession;
PMPrintSettings printSettings;
PMCreateSession(&printSession);
PMCreatePrintSettings(&printSettings);
PMSessionDefaultPrintSettings(printSession, printSettings);
CFArrayRef paperList;
PMPrinterGetPaperList(myPrinter, &paperList);
PMPaper usingPaper;
//iterate over paperList and to set usingPaper to the paper desired
PMPageFormat pageFormat;
PMCreatePageFormatWithPMPaper(&pageFormat, usingPaper);
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)incomingPrintData);
PMPrinterPrintWithProvider(myPrinter, printSettings, pageFormat, (CFStringRef)kMimeType, dataProvider);
}
(via Core Printing Reference)
Beware this code lacks memory management so you will need to use the PMRetain() and PMRelease() functions as well as the CoreFoundation memory-management functions as well.
If anyone can tell me how I can get data from the OSX print dialogue into data I can use in this method I'll accept their answer instead of this. That is, without using Carbon functions.

Opening a file using ScriptingBridge

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)

Get Path to selected file in Finder

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.

How to implement a Cocoa-based Adobe Photoshop plugin

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.