Swing menus, Actions, and enabled/disabled state

I've never liked toolbars. They inhabit this gray area for me between menus (where commands I hardly ever use live) and the keyboard (where commands I use all the time live). And I've never really got the point. Especially when they so often consist of just a row (or rows) of impenetrable little hieroglyphs. When a toolbar has text under the icons, I'm less likely to find out how to turn them off, but this just seems to mean that I never get round to learning the keyboard equivalents because, unlike menu items, toolbar buttons don't teach you how to work better.

So my programs have never had toolbars. They often didn't have menu bars (as opposed to pop-up menus) either, but becoming a Mac user changed that. If I didn't have reason to believe there would be an outcry from users, I'd probably have changed the Terminator terminal emulator to always have a menu bar even on Linux by now.

Anyway, Terminator does have a menu bar on Mac OS, and the other week I tried to automatically enable/disable "Select Next Tab" and "Select Previous Tab" on the "Window" menu, depending on whether or not there were any tabs to move to. Usually I just override Action.isEnabled and I'm done, but that's because I have special code in the Edit editor to make this possible. It's been like that since about 1998, and I'd pretty much forgotten it's not representative of how Swing works.

Swing, of course, supports toolbars, so you're supposed to call Action.setEnabled. The trouble with this is that it encourages code that I've always felt should be in the Action to leak out into the rest of program. An Action ought to know for itself when it should be available, rather than be told. (The idea of duplicating some of the program's state always seems slightly offensive too.)

A very similar alternative is to make the Action know more about the rest of the program; it could listen to other components or collections, and although this is definitely better than hard-wired interdependencies, it still seems like you're spreading what's conceptually a single piece of functionality all over the code. too complicated a web of listeners (and the notification storms that can result) is a definite bad smell.

It's interesting how many times "isEnabled" boils down to a dependency on the size of some collection. Be it the size of the undo buffer, the redo buffer, the number of tabs in some JTabbedPane, the number of Frames, or the existence of a selection. One way to ameliorate the "web of listeners" problem is to make the collections vend suitable actions. So the undo/redo buffers offer undo and redo actions, for example, and it doesn't seem at all unpleasant that they should have interdependencies because they're in a conceptual "undo/redo system".

The way a framework like Apple's Cocoa takes care of all this nonsense for you and hides it from you is one sign of how much more sophisticated it is than Swing.

The Mac actually illustrates the other common example of "isEnabled": implementing modes. If you look at the menus in Mail, for example, lots of functionality will be disabled if the Inbox has the focus, but enabled if a composition window has the focus (at which time, some of the functionality that was previously enabled will have become disabled). As displays grow larger, my doubts about the screen menu bar (and Apple's style of making it the union of all possible windows' menu bars) grow too. Modes aren't necessarily evil, but when it's not clear what they are, which one you're in, or how to move between them, you're at the evil end of the modality spectrum.

Anyway. To support a style where Action classes determine their enabled/disabled state themselves, Edit has long used a MenuListener to traverse a menu that's about to be displayed and call Action.isEnabled on each menu item's Action, and then call JMenuItem.setEnabled on the menu item itself. It's not perfect, but it's pretty good. The only limitation as far as menus are concerned is that you can't use this to disable a top-level menu (that is, a menu that's attached directly to a JMenuBar) because that's always visible, so there's no menuSelected notification to act on.

And, of course, there's the fact that if you want to use your Action on a toolbar, it won't correctly enable/disable itself.

So you can't win, but you do at least have a choice if you can live without toolbars (and as a developer but especially as user, I can easily live without toolbars). The code to implement this "pull" style is now in salma-hayek, if you'd like to use it in your own programs. If you have a better alternative technique, I'd be interested to hear about it.