Interprocess Dragging is a Drag (Sometimes)

September 23rd, 2005

My “Apple Bug Friday” entries today are both related to a really cool feature of Mac OS X: process switching while dragging. As the operating system has evolved, dragging in general has improved. One of my favorite improvements is that while dragging you can now invoke the Cmd-Tab process switcher keys to bring another application to the front. So instead of performing that tedious “line up” to prepare two applications for a drag, you can simply start in one application with the knowledge that you’ll be able to navigate to the other app before your finger releases the dragged item. I often use this, for instance, when sending attachments through Mail:

  1. Locate the attachable files in the Finder.
  2. Select them and start a drag.
  3. Cmd-Tab to activate Mail.
  4. Cmd-N to create a new mail message.
  5. Drop the drag into the content of the new message!

It’s just … downright amazing that you can do this! So where are the bugs? There are two major shortcomings I’ve observed in Apple’s overall fantastic implementation:

Radar 4270813 – Can’t cancel drag outside of initiating application

Normally when you start a drag and end up with second thoughts, you can rely on the effectiveness of the escape key to reject the drag without any worries that you might be accidentally moving a file or object to some new, unfathomable location. For some reason this mechanism simply stops working once you’ve switched to an application other than the app that originated the drag. So, assuming I want to safely cancel a drag that I’ve started and then switched applications for, I have to now navigate back to the original application just to hit the escape key and cancel the drag!

For instance:

  1. Navigate to the Finder and begin dragging a file.
  2. Cmd-Tab to another app, say, Mail.
  3. Hit the escape key. No effect.
  4. Cmd-Tab back to Finder.
  5. Hit the escape key. Drag cancelled!

What a drag! The cancel key should kill the drag safely regardless of where the user has wandered. This is especially problematic if, for some reason, your short-term memory fails you and you can’t remember which application originated the drag.

Radar 4270824 – Can’t completely switch back to drag-originating app

So, once you switch back to the originating app you can hit cancel and breathe a sigh of relief. The problem is, switching back to the originating app while the drag is still in place produces another bug, which is that although the originating app is now “front-most”, none of its IU (not even its menu bar) has yet been brought to the front. So it’s not only confusing, but if you decided you wanted to drag to the originating app after all, you’re out of luck. You’ll have to cancel the drag and start over.

Illustration:

  1. Initiate a drag in the Finder by selecting and dragging a file from a folder list.
  2. While keeping the drag alive with the mouse, Cmd-Tab to another application, say Safari.
  3. Decide you don’t want to drag to Safari after all, and Cmd-Tab back to the Finder.

Result: Although the Finder is somewhat active, none of its windows have come forward, and the menu bar still says “Safari.”

Workaround: If you can glimpse any part of a Finder window’s content, then dragging the item to this content and “moving it around” seems to trigger full activation of the Finder. I imagine it’s similar for other applications.

What a drag! Cmd-Tabbing to an application should always bring it forward.

Destroy Xcode Tedium

September 19th, 2005

If you’re developing applications on the Mac, then you’re either familiar with, or about to become familiar with Xcode, Apple’s free development environment. I have been using Xcode for about five years now, if you count the time I spent with its predecessor, Project Builder. I am more or less happy with it, though of course I have my complaints.

Among the best changes in Xcode over the past few years has been increasingly improved scriptability, which means that the minor complaints one might have can often be remedied through a bit of automation. One of the things that has always bothered me about developing applications on the Mac is making certain that, with each version change, the correct values propagate to all the required points in the project. This is one of the most tedious and error-prone “chores” that needs to be tended to on a repeated basis. It used to mean changing ‘STR#’ resources to match ‘vers’ resources, and so on and so forth. On Mac OS X and with Xcode, it’s been pretty much reduced to editing a couple Info.plist keyed values: CFBundleGetInfoString and CFBundleShortVersionString.

Two is a nice, low number. Easy to manage, but it still means that whenever anything changes, it has to change in two tediously manual steps. Wouldn’t it be nice if you could store the version string for your application in one spot and have it be guaranteed that it would automatically appear in those two Info.plist keys?

Thanks to Xcode’s build value string substitution and its AppleScript scriptability, you can not only achieve this goal, but you can do it with panache and flair! The following steps will explain one method for easily configuring your projects such that, for all eternity, you can simply and quickly change the version string to whatever you desire with the flick of the wrist or the pressing of a keyboard shortcut.

1. Define a build setting for your application’s version string.

This will serve as the “central point of change” for your application’s “logical version number.” Examples of what this value may be set to include “1.0” or “1.7b1”, or even “1.Booyeah”. It’s up to you – whatever you consider the “base” version string to be, which you would normally go in and manually type into the two Info.plist keys mentioned above, you will now set as a separate, custom build setting. I named mine “APPLICATION_VERSION” and added it to the project’s build settings for “All Configurations.” Click the Top-Level project icon in Xcode, open the “Info” inspector, and open the “Build” pane. Now click the plus-sign icon to add a new setting, and type whatever name and initial version string you desire (or leave it bogus for now to prove that the script described below is working).

2. Replace hard-coded version strings with build-setting substitution markers.

Now that you’ve given Xcode an “APPLICATION_VERSION” or otherwise named build setting to store your version information inside of, you can replace any hard-coded version strings in your Info.plist file with the appropriate variable-expansion shorthand. For example, in my FastScripts project, I include the following declarations for the two Info.plist keys mentioned above:

<key>CFBundleGetInfoString</key>
<string>FastScripts ${APPLICATION_VERSION}, ©2003-2005 Red Sweater Software</string>
<key>CFBundleShortVersionString</key>
<string>${APPLICATION_VERSION}</string>	

(Note that because of a shortcoming in Xcode, this type of variable substitution will not be performed on your InfoPlist.strings file. This is a pretty good argument for moving these keys to the Info-MyProductName.plist file, though you will lose the ability to localize these keys in the process.)

You should now be able to build and run your application, open the “About Box” and observe the version strings were correctly substituted into your Info.plist keys. Go ahead, change the APPLICATION_VERSION build setting to something else, build and run again. Wasn’t that easy? It is easy, but not quite easy enough for my tastes.

3. Automate setting the APPLICATION_VERSION build setting.

It’s less tedious, but still annoying to have to go open the project inspector, locate the APPLICATION_VERSION build setting, and change it manually every time you want to release a newly versioned edition of your product. To make this truly as easy as developing on a Mac should be, we want to take advantage of Xcode’s AppleScript support to essentially add the “Change Application Version” dialog to Xcode. The following script accomplishes this. It asks the user for a new version string, and then iterates through all build configurations that advertise an APPLICATION_VERSION setting, changing the setting to the newly specified value.

Copy and paste the script below into your Script Editor, and run it. You will be prompted for a new string which, when the script finishes, should be set as the value of the APPLICATION_VERSION setting.

tell application "Xcode"
	set targetProj to project of active project document
	
	set newVersion to text returned of (display dialog "Enter the new Application Version value:" default answer "")
	
	-- For any build configuration that has an APPLICATION_VERSION build setting, 
	-- change it to the new value.	
	repeat with thisConf in build configurations of targetProj
		try
			set thisVersSetting to build setting "APPLICATION_VERSION" of thisConf
			set value of thisVersSetting to newVersion
		end try
	end repeat
end tell

Fantastic, isn’t it? Of course, nobody wants to fire up Script Editor just to run a script that modifies a build setting in Xcode. That’s what the Script Menu (or the BMW of script menus, FastScripts) is for. From FastScripts, you can even assign an Xcode-specific keyboard shortcut, putting the functionality at your fingertips.

I’ve gotten in the habit of setting up all of my projects with an “APPLICATION_VERSION” build setting in this manner. I drop the above script into my Xcode application-specific scripts folder, and use FastScripts to configure Ctrl-V as an Xcode-specific trigger for running it. This essentially extends Xcode’s functionality for all of my projects such that changing the version on any of my projects is as easy typing Ctrl-V and typing the new string.

I will never have to worry about mismatching version strings again, and I can spit out a newly versioned edition with nary a whimper of tedium. Isn’t silence beautiful?

Update Now that you’ve got your marketing version in order, why not integrate Subversion’s revision number into your bundle version?

Self-Opening AppleScript Droplets

September 11th, 2005

Most of the AppleScripts I use are “plain scripts.” That is, they require a host application in order to be run. AppleScript also supports the notion of a script application, which can optionally accept “dropped” items directly to the script’s icon. These script applications are commonly referred to as “droplets.”

Most of my droplets are “pure droplets.” By that I mean, they are completely useless to me unless I have actually dropped something on to them. Because of this, and because of the fact that I’m in the habit of double-clicking “plain scripts” in the Finder to bring them up in Script Editor, I often find myself accidentally double-clicking my droplet scripts in an attempt to edit them. This has the unfriendly result of my script simply complaining that it didn’t get anything dropped on it. I then have to go find the Script Editor and drag the droplet to it in order to edit it.

It occurred to me today that I could use the script itself to achieve the desired result. Instead of responding to a double-click launch with no “dropped items” by failing, why not just do what I want it do? I added code to the top of one of my pure droplets, which yields the desired effect:

tell application "Script Editor"
    activate
    open (path to me)
end tell
return

on open (dropArgs)
	-- Droplet code goes here
end open

Now when I double click the droplet, it kindly asks the Script Editor to open itself for editing. This extra code has no impact on the “useful” droplet code, because a droplet’s code is reached through an “on open” handler, which means the implicit “on run” code at the top-level of the script is never run except when the item double-clicked.

Safari Link Sniffing

September 9th, 2005

Keeping with the Javascript theme of my last entry, I’d like to share one of the ways I’ve used Safari’s AppleScript accessible javascript model to improve the quality of my web browsing experience.

It occurred to me one day while reading some long article on Salon.com that something had been bothering me for a long time. The way that web sites often force you to split up your reading into several pages, and the fact that almost every web site seems to have a different link name, location, etc., for performing this simple and extremely frequent task.

Why isn’t there a standard way to “go to the next page” in a web browser? We have forward and backward, but we have no way to get to the forward part of that equation unless we’ve already been there! Useless!

I decided to try my hand at automatically detecting the location of the “Next” button, and clicking it on the user’s behalf. Why should I, a human being with precious little time on this planet, have to waste huge chunks of it hunting around for links named “>>” or “Next >” or “Page “?

My solution hinges on the fact that javascript exposes a given web document’s links in the form of a “document.links” array. Not familiar with Javascript? Try typing the following into your browser’s address bar and press return:

javascript:alert(document.links[0]);

A dialog should appear showing you the location of the first link on this page. OK, good. But that’s useless for my purposes. I need to know what the link looks like to the user. Does it look like a “next” link? Try this javascript command next:

javascript:alert(document.links[0].innerText);

This bad boy will pop up the text as it appears (more or less) to the user. Great! So all I have to do is iterate through *all* the links on the page, look at their innerText, and if it looks like “Next” or any of the bajillion other variations on “go forward” that are currently in use on the web, then I’ll follow the link.

This approach basically works, and after collecting a number of examples from around the web, I came up with a pretty good, general purpose script for doing exactly what I wanted.

I shared the script through MacScripter.net and have found quite a few satisfied users who have also contributed by pointing me to sites that were not previously supported. The script now looks not only for links with names like “Next >” but also for graphical links whose files are named “next.gif”, etc. The result is a script, free for your use, which can take the tedium out of “going to the next page” for lots of the sites you access in Safari. It works best with a keyboard shortcut. For instance, I set mine up to trigger when I am in Safari and hit “cmd-right-arrow.” You can download Safari Next Page from the Red Sweater Scripts page. In addition to long stories on the web, it’s also great for search engines that return dozens of pages that you need to cruise through quickly.

If you use it, let me know when you find sites that can’t be tricked by it. I will do my best to add support in a general way that fixes it for the site in question and hopefully many others.

Update: Following a clue posted by Faried below, I found a rather more extensive version of this project for the Firefox browser. There are two similar projects both worked on by the same folks: Link Toolbar and LinkIt. Unfortunately, the Link Toolbar seems firmly entrenched in MPL/GPL/LGPL licensing restrictions. I can’t tell yet whether the LinkIt accumulated wisdom could be shared or not. I am impressed by the fact that they’re using regular expressions to do the comparisons. Much slicker than my brute-force method.