I’ve built a custom Automator workflow editor and I’d like to have it prompt the user to Save, if changes were made to the workflow. before switching to another workflow or closing the window.
Since the UI elements aren’t referenced in my xib and loaded in an AMWorkflowView, I’m having trouble figuring out how to get any notification when a change is made to the action’s UI (check boxes, textfields, popups, etc).
I haven’t seen anything regarding how to this and Apple’s samples have convieniently set their sample code to: workflowView setEditable: NO
Here’s the code I’m using to initialize the workflow show it in the workflow view:
set theSelectedWorkflow to (current application's class "AMWorkflow"'s alloc)'s initWithContentsOfURL_error_(theSelectedWorkflowURL, missing value)
workflowController's setWorkflow_(theSelectedWorkflow)
The workflow view is in a split view with a source list on the left kinda like Automator.app. I’ve got a beta out for testing and I’d be happy to share the link if it would help illustrate what’s going on.
If anyone can help offer some advice I’d be forever grateful!
I am trying to get a workflow into a workflowView, but have been unable to do it so far. I implemented the code you posted above, added the automator framework, and added a workflowView and workflowController to my xib file. I tried getting a workflow from Apple’s AutoSample project, but I’m not sure what I should see in my window. If I log the workflowController’s workflow() I get this:
But, I don’t see anything in my workflowView. Should I? Is there something else I need to do to hook things up?
It appears that the action IS loaded, just not being shown in the view. It should present the workflow as if you opened it in Automator, showing the workflows actions and any options that were saved with the workflow. Service workflows will show the service input options at the top as well.
I’ve also got the following properties in my script:
property amWorkflow : class "AMWorkflow"
property workflowView : missing value
property workflowWindow : missing value
property workflowController : missing value
In my app, I’ve got a blank workflow in my resources folder that gets loaded if there are none of my supplied workflows found in the user’s ~/Library/Services folder. I don’t think it matters, since it looks like you already have the workflow loaded in the controller, but I thought I’d post the code I use for this just in case:
set theSelectedWorkflowString to POSIX path of (path to resource "blank.workflow") as string
tell current application's NSURL to set theSelectedWorkflowURL to fileURLWithPath_(theSelectedWorkflowString)
set theSelectedWorkflow to (current application's class "AMWorkflow"'s alloc)'s initWithContentsOfURL_error_(theSelectedWorkflowURL, missing value)
workflowController's setWorkflow_(theSelectedWorkflow)
mainWindow's setTitle_("No Workflows found")
tell theTable to reloadData()
tell mainWindow to displayIfNeeded()
Did you have a chance to take a look at my app? You can view, edit and save the workflows. It might help if you could see the end result.
Thanks!
Robert
EDIT: In case anyone stumbles across this, you also need to link to the Automator.framework to your project.
I have all those connection, but I still see nothing. I added a toolTip to the workflow view, and I don’t see that either if I hover over the area where it should be. I then tried adding the view and the controller in code instead of IB and doing all the hookups there – still nothing. Here is the code I tried:
script AutomatorEditorAppDelegate
property parent : class "NSObject"
property wfController : missing value
property flowView : missing value
property win : missing value
property theTip : "This is a tooltip"
on applicationWillFinishLaunching_(aNotification)
set wfController to current application's AMWorkflowController's alloc()'s init()
set flowView to current application's AMWorkflowView's alloc()'s initWithFrame_({{62, 323}, {373, 265}})
flowView's setToolTip_(theTip)
flowView's setEditable_(1)
win's contentView's addSubview_(flowView)
wfController's setWorkflowView_(flowView)
set flowView's workflowController to wfController
wfController's setDelegate_(me)
set thePath to POSIX path of ((path to desktop as string) & "test.workflow") -- I got htis test file from your app and saved it to my desktop
set theSelectedWorkflowURL to current application's NSURL's fileURLWithPath_(thePath)
set theSelectedWorkflow to current application's AMWorkflow's alloc()'s initWithContentsOfURL_error_(theSelectedWorkflowURL, missing value)
wfController's setWorkflow_(theSelectedWorkflow)
log wfController's workflow()
log flowView
log wfController
log flowView's workflowController
log wfController's workflowView()
log flowView's toolTip()
log win's contentView()'s subviews()
win's displayIfNeeded()
end applicationWillFinishLaunching_
end script
And, here is the log:
Everything looks ok to me, so I can’t see why it’s not working.
Unfortunately I don’t have much experience with this to troubleshoot it.
One thing I noticed is that the Add TV Tags action you referenced is a custom action included in the bundle. If the action isn’t loaded in Automator’s Library you may have a problem there.
Did you try the sample project I PM’d you this morning? It might be easier to work with.
I can’t run that project because I’m still using Xcode 3 (under Lion). It looks like you have the same workflows folder in there as is in the Apple AutoSample project. I’ll try putting that folder in my bundle and see if that makes any difference.
I had a few thoughts on how you might solve your problem – it’s hard for me to test any of this, since I can’t get the AMWorkflowView to work, so the only way I can see that you can check if the user has made any changes to the workflow is to look at the workflow file. NSFileManager has a method, contentsEqualAtPath_andPath_, which checks to see if the contents of two files are the same. With saved files on the desktop this seems to work fine. I saved a workflow from your app under 2 different names, then made a couple of changes (checked some check boxes) and saved it as a third file. The above method returned 1 if I compared the first 2 files but 0 if I compared 1 or 2 with 3. I don’t know if there is a way to compare the workflow in your view with a saved file though. You might have to save the workflow as a tmp file, do the comparison, discard the tmp file if they are are the same and ask the user if they want to save if they are different. You would have to implement this in an applicationShouldTerminate method and also wherever you do the swap out of workflows if the user chooses a new one.
Thanks so much for your help! I’ll try the temp save method. My concern is that it might slow things down. Is there a way that I could detect if the user clicked in the workflow view, then only run the comparison if they did?
Unfortunately, I can’t think of a way to do that easily. I finally did get the workflow to show up – there was a missing plugin in IB that got loaded when I opened the nib file of the apple example AutoSample.
I did find a way to determine if a user clicks in the workflow view – it seems like there should be an easier way to do this, but this does seem to work. What I did was add an applescript class of the NSView type to the project called Overlay. I then dragged a custom view over the workflow view in IB and set its class to Overlay (and be sure to set its stretchiness to the same as for the workflow view’s scroll view). This is the code i put in the Overlay class:
script Overlay
property parent : class "NSView"
on drawRect_(rect)
current application's NSColor's colorWithCalibratedRed_green_blue_alpha_(0, 0.4, 1, 0.2)'s |set|()
current application's NSBezierPath's fillRect_(rect)
end drawRect_
on mouseDown_(theEvent)
set current application's NSApp's delegate()'s didClick to 1
setHidden_(1) -- doesn't receive mouse events after it's hidden
continue mouseDown_(theEvent)
end mouseDown_
end script
Because the overlay view sits on top of everything, it gets the mouse events first. In the mouseDown method I set the value of didClick to 1 (that’s a property in the app delegate). The view then hides itself so it won’t get any more mouse events, and then the continue statement sends the event up the responder chain to the object that was under the mouse pointer which gets that event just as though the overlay view was never there, but you’ve set a flag indicating that the click was made.
In the app delegate I added just a few things to the code that I posted before:
property didClick : 0
on applicationDidFinishLaunching_(aNotification)
win's makeFirstResponder_(theOverlay)
end applicationDidFinishLaunching_
on applicationShouldTerminate_(sender)
log didClick
return current application's NSTerminateNow
end applicationShouldTerminate_
I added the property didClick, set to 0 and just logged it when I close the app for testing purposes. I added the applicationDidFinishLaunching method because setting the first responder to the overlay view in IB didn’t seem to be reliable. This seems to be a new behavior in Lion where the window “remembers” things like its size, position and current responder between launches of the program (I didn’t notice that before Lion anyway). Putting that in the “will” version of that method didn’t work whereas the “did” version did (a timing issue I guess).
The drawRect method in the Overlay class is just in there to color the Overlay so I could see it for testing. That method can be deleted. Without that method the view is completely transparent, so you can’t see it whether it’s hidden or not.