droplets, persistent user defaults, and bound variables

Here’s the design problem I can’t solve:

I want to have a dropplet the user can make a copy of (“duplicate”) and change it’s preferences. The problem is that in Xcode, all copies of an application will share the same user-defaults plist! Thus when the user changes the preference in one dropplet it changes for all of them.

To make this concrete. Suppose the dropplet had some user selectable actions that it could take when something was dropped on it. for example, let’s say the preference panel of the dropplet had the following items:

  1. a checkbox labeled “print dropped files”
  2. a check box labled “move dropped files to folder”
  3. a textbox that contained the name of the folder to move to.

Now the user could make several copies of this, put them on his desktop, then set the preferences. Some dropplets would move the files dropped on them to particular folders, other would simply print the document, others would do both. Not a very useful dropplet but you get the idea.

But wait how do we store all those preferences persistently. Well we could use the user defaults class. this is especially handy if we are using bound variables. But this stores the preferences in ~/Library/xxx.plist where xxx is some chosen target name.

the trouble is when the user copies the application, the preference plist stays the same. So all the dropplets read and write to the same prefernces file.

How do I get separate preferences for each dropplet??? preferably I’d like to do this without losing the variable binding in the interface builder.

cems2, there was another thread here not a week ago that discussed storing preferences in the app bundle itself. You could create a plist file and place it in the resources of your app, and then reference that file relative to the app’s path. This is only supported under tiger, if that’s an issue for you. If you must develop for older os versions, you’ll have to use the ‘defaults’ unix command via a shell script.

Depending on the complexity of your app, there could be some problems with having multiple (potentially many) copies of the same app to do essentially the same problem. This sounds like poor design technique to me, could be addressed pretty easily. Perhaps when someone drops something on the app, it opens a small window which allows the user to select which set of evaluation criteria to use… like a popup button or table view… and then the user clicks “go” and then app reads those criteria from the main defaults and acts accordingly. Having multiple apps to do minor variations of a single action seems like an unnecessary kludge to me.

j

The key to doing this is to assign unique bundle identifiers to each copy of the application. Here’s a little script that can be saved as an application. Drop your application onto it and it will duplicate the application, name it sequentially, and give it a unique bundle identifier so its preferences will be distinct from other copies (update the app name and bundle identifier, of course):

property app_name : "App X"
property app_bundle_identifier : "com.developer.AppX"

on run
	open {(choose file of type {"APPL"} with prompt "Locate your copy of " & app_name)}
end run

on open the_app
	try
		set the_app to (item 1 of the_app) as alias
		tell application "Finder"
			set container_path to (get container of the_app) as Unicode text
			set i to 1
			repeat
				set i to i + 1
				set the_name to app_name & i & ".app"
				try
					get (container_path & the_name) as alias
				on error
					exit repeat
				end try
			end repeat
			set the_app to (duplicate the_app)
			set name of the_app to the_name
		end tell
		set the_plist to (container_path & the_name & ":Contents:Info")
		do shell script "defaults write " & quoted form of POSIX path of the_plist & " CFBundleIdentifier " & app_bundle_identifier & i
		activate
		display dialog app_name & " has been duplicated and you may now launch " & the_name & " along with the original " & app_name & " application to see multiple instances of the application." buttons {"OK"} default button 1 with icon 1 giving up after 10
	on error e
		display dialog e buttons {"OK"} default button 1 with icon 0 giving up after 10
	end try
end open

Jon

Thanks for the replys Jonn and Jobu.

Jobu:
thanks for the tip on the plist writer/reader.

Jonn:
I think I see your strategy, but I’m still puzzling over what it is doing. Confused at the cfbundle identifier step.

Now this has helped me it has not solved the problem I meant to pose. let me restate the problem and then show you the issues. THe goal is to have some sort of dropplet I can replicate, launch and configure its preference and have it stand-alone. I also want to use bindings in the interface builder. Now when I say stand alone I mean it. I’d like to be able to configure the dropplet, drop it in the mail, and the new user can run it as I configured it out of the box as well as replicate and reconfigure it.

Here’s the issues that your suggestions dont really address.

Jobu: While I could do as you suggest an keep a separate local plist outside the library, and I could even hide that inside the droplet itself so it would be portable, this does not fix the issue of how to do bindings to the interface builder fields. What would be snazzy is if I could somehow bind the interface builder to this local plist rather than to the user defaults one in ~/Library/Prefences. As it is what I first tried to do was have a local plist, on launch copy this into the user defaults, on quit copy it the user defaults back to the local plist (to capture any changes on the interface). That kludge works unless you happen to have two copies of the dropplet open at the same time–in which case they are sharing the user Defaults. Nuts! How do I bind the user interface to the local plist instead of the user default or equivalently how do I tell the user defaults to use the local plist instead of the one in ~/Library?

Jonn:
It looks like this would not be self contained. if I mailed the app it would lose the prefernece settings I think. I also don’t understand how this works with the bindings. But maybe you have solved one of the problems above. does your solution somehow change the target plist name for the user defaults so that each copy would write to a different user default plist. At least that way my current kluge could have more than one copy of the application open at a time.

thanks! I hope I hear back form you

I guess I should say what I’m going for here. The idea is to create a general purpose dropplet that I can reconfigure versitally with without actually editing the program. this would have a variety of uses. For example, if I wrote a perl script or had an existing one, I could drag it inside the application .app folder, launch the dropplet application and point it at the script, and maybe set some configuration fields (like run with admin privledges, or show output from script, maybe some command line flags, …).

then whammo I have a dropplet that sends files and folders dropped on it to the script.

It’s all self contained so I can copy it and make verisions of it that can be placed in different locations or sent to other people and they run as I configured.

I’ve got wads of administration scripts I sometime need to send to people to use who could not use a script but can drag and drop. so rather than creating a custom apple script for each one, I write a reconfigurable dropplet.

This was really easy to do in applescript actually as long as the interface could be done with just dialog boxes. the headache happened when I switched to applescript studio and lost the persistent properties and had to deal with bindings to the interface and user defaults.