Skip to main content

Mnemonic Magic

Posted by enicholas on June 5, 2006 at 10:18 PM PDT

Swing's mnemonic system is based around two properties: mnemonic (or displayedMnemonic) and displayedMnemonicIndex. They're powerful enough to do everything you need, but then again, so is machine code. There are a number of problems and limitations with the skeletal mnemonic support built in to Swing.

The Problems

  • They're annoying to define - One common approach is to use a properties file to define actions. You generally end up with a property for the action's name, a property for its mnemonic, and an (optional) property for its mnemonic index -- three properties for the name of each action.
  • It's easy to accidentally duplicate mnemonics - if you add new actions, or build a new menu from existing actions, it's incredibly easy to forget that (say) both "Lock" and "Layout" are using 'L' as their mnemonic character
  • It's difficult to spot mistakes - the only visual cue to a mnemonic is an underline. Using Windows XP with default settings, the underline doesn't even show up unless you hold the Alt key. Mistakes are easily overlooked, and there is no error reporting.
  • The same action may need different mnemonics in different contexts - If an action appears (say) in both a menubar menu and a right-click context menu, a mnemonic character which works in one menu may conflict in the second menu. I once ran into a menu item with a short name which, in a particular context menu, conflicted with other menu items on every single character in its name. With the action defined by static properties, though, you have to use the same mnemonic everywhere... so what do you do? Not have a mnemonic anywhere just because it conflicts in one rarely accessed location?
  • It poses serious problems for internationalization - A quick survey of the bug database turned up a number of bugs relating to incorrect translations of mnemonics and mnemonic indices: 6411189, 4983399, 4664265, 4735183. There are two general problems: first, all of these properties are interrelated, and it's easy to miss one of them during translation -- leading to incorrect mnemonics or (worse yet) a mismatch between the actual mnemonic and the underlined character. Second, translation is quite complicated because the translator has to check that the mnemonic doesn't conflict with any other mnemonics in every menu in which the action appears. If the folks at Sun are making these kinds of mistakes, what hope is there for the rest of us?

Because of these annoyances, the unfortunate fact is that most Swing programs (at least among the ones I've seen) don't actually use mnemonics at all. That's sad, especially when you consider that other toolkits don't seem to share this aversion for mnemonics.

The Solution

The solution I propose is twofold. First, combine all three properties into a single value. Second, eliminate the need to specify mnemonics at all in most cases.

Combining all three properties is quite easy. You just need to embed the mnemonic and mnemonicIndex information into the label itself.

Before:

action.saveAs.name          = Save As...
action.saveAs.mnemonic      = A
action.saveAs.mnemonicIndex = 5

After:

action.saveAs.name = Save _A_s...

I chose to use a pair of underscores surrounding the mnemonic character to identify it, as opposed to most toolkits which use a single escape character. Windows, for example, would have specified the label as "Save &As...". The advantage of the underscore encoding is that there is essentially no chance of ever "accidentally" specifying a mnemonic character, whereas you might legitimately try to use an ampersand in a control, which in Windows would lead to an incorrect mnemonic.

Being able to specify the mnemonic character in this fashion carries a lot of benefits. Because it's a single value, specifying it in properties files is much easier, internationalization is much easier, and it's impossible to accidentally have a mismatch between the index and the mnemonic character. While this is much easier to deal with than three separate properties, it doesn't solve some of the other thorny problems, like trying to prevent duplicate mnemonics.

The rest of the solution is automatically assigning mnemonics. This is easier than it sounds, because we tend to follow certain heuristics when assigning mnemonics to actions and labels, and we can just turn those heuristics into a program. The algorithm I use is as follows:

  1. Try the first character in the string. Check to see if it is available (among other menu items in the menu or components in the window, as appropriate).
  2. If that isn't available, try capitalized letters in the order in which they appear.
  3. If no capitalized letters are available, try lowercase consonants.
  4. If no lowercase consonants are available, try lowercase vowels.
  5. If no lowercase vowels are available, give up.

So my framework automatically assigns mnemonics to all actions (and specified components) based on these rules. 99% of the time it picks the same mnemonic you yourself would have. If it doesn't do what you want, you can manually specify it using a pair of underscores around the character in question. You can also force it not to choose a mnemonic at all by putting two underscores in a row at the end of the string. This makes translators' lives far easier, because they can generally just not worry about mnemonics and let the program "do the right thing". Furthermore, my framework has automatic error checking and will, if assertions are enabled, throw an AssertionError when a menu or dialog contains duplicate mnemonic characters.

Unfortunately...

There are some other details, of course, like the API calls necessary to actually work with this framework, but they aren't relevant because I can't release the code to the public. It's part of a proprietary application which my employer owns the rights to, so you'll just have to use your imagination. Fortunately the code itself is relatively straightforward -- you could easily duplicate this setup if you wanted to, and probably save yourself a lot of time overall (if you're working on a big application). There is hope, however...

JSR 296

JSR 296, "Swing Application Framework", aims to (among other things) define a standard way of representing and managing Actions. My hope is that, as part of this, JSR 296 will include a better means of managing mnemonics than having to specify three properties individually. At the very least I'd like to see the mnemonic be able to be embedded directly into the string (as in "Save _A_s...", "Save &As...", or something equivalent), but trust me when I say that the automatic mnemonic management is incredibly nice also.

Considering that I have a seat on the JSR 296 expert group, I will definitely be pushing for some sort of solution to these issues. I haven't actually discussed this with anyone yet, though, so maybe they already have an even better solution in mind (yay!) or have decided that this issue isn't worth pursuing (boo!). It's also entirely possible that I'm the only weirdo that considers this a significant issue in the first place.

JAXX

If you've used my JAXX framework, you may be a little surprised about this entry, considering that JAXX doesn't do automatic mnemonic management or allow you to embed mnemonic markers directly into the label. That was a deliberate, albeit painful, decision, and it comes back to JSR 296. Given that there's eventually going to be a standardized Swing application framework, I'm taking a wait-and-see attitute, carefully avoiding doing anything in JAXX which might conflict with the Swing framework-to-be. If JSR 296 does define a standard way to identify mnemonics, I'll use the same syntax. And if it doesn't, I'll do it my way. Regardless of the outcome, I expect that JAXX will eventually support this sort of mnemonic magic.

What do you think?

So, what do you think? Do you agree that Swing's approach to mnemonics could use some improvements? Want automatically managed mnemonics? Hate the idea? Like being able to specify mnemonics directly in the string, but don't like the syntax? Or am I just altogether crazy?

Related Topics >>