Deprecated: Function set_magic_quotes_runtime() is deprecated in /home/xeno04/ on line 14
Walker Software Weblog: Giving JTree Fitts

Giving JTree Fitts

Sep 22, 05:15 PM

Swing ignores most human-computer interaction theories. A case is point is JTree (or even JList) and Fitts’ law. If you have programmed rich clients in Java, you’ve probably run across the annoying fact that putting an empty string in JTree or JList makes that item hard, if not impossible, to select.

Fitt’s law is a mathematical model of human muscle movement. The Wikipedia link above has more information, but the basic lesson is that bigger targets are easy to hit (also screen edges and corners are the biggest targets, but that is not pertinent to our problem). Fitts’ law is used to good effect in most modern OSes. In Mac OS the menubar is along the top edge, the dock item’s hotspots extend to the edge of the screen, the old thin window borders are gone in favor of easier to hit “grow box”, etc. In Windows, if you slam the down mouse down into the corner, you can click the “start menu”. Both OSes allow the user to select a tree item by clicking anywhere on the row.

There is an absence of Fitts’ law in Swing. It’s not really surprising, Swing was created around 10 years ago — before this sort of stuff was integrated into GUI toolkits. It’s one of the reasons some users dislike Java applications, although they may not be able to put their finger on it. Lets say for instance, you have a 300 pixel wide list (or tree) and the item you want to select is the letter “a”. The ideal target area would be the width of list times the height of the row (as it is in both OS X and Windows XP), but the problem in swing is your target area is the width of the letter “a” times the height of the letter “a”. (If I recall, Windows 95 behaved like Swing does.)

In a previous post, Tiger Trees, I posted code to make JTree look more like the tree in iTunes. Not only did I ignore the mouse selection difference, I had a bug in the code (hence, the scrolling problems I mentioned in that post). To summarized what code is needed, we must override a few of the painting methods to draw the selection across the entire width of the JTree, and create a new mouse listener.

public class TigerTreeUI extends BasicTreeUI{

/** Creates a new instance of TreeUI */ public TigerTreeUI() { super(); } protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int param, boolean param6, boolean param7, boolean param8) { boolean selected = treeSelectionModel.isPathSelected(path); if(selected){ DefaultTreeCellRenderer ren = (DefaultTreeCellRenderer)tree.getCellRenderer(); Color c = ren.getBackgroundSelectionColor(); g.setColor©; g.fillRect(0,bounds.y,tree.getWidth(),bounds.height); } // Do not call super. We don’t want the lines. } protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) { // Do not call super. We don’t want the lines. } protected java.awt.event.MouseListener createMouseListener() { return new FittsMouseListener(); } protected class FittsMouseListener extends MouseAdapter{ public void mousePressed(java.awt.event.MouseEvent mouseEvent) { handleSelection(mouseEvent); } protected void handleSelection(java.awt.event.MouseEvent evt){ if(tree != null && tree.isEnabled()) { if (isEditing(tree) && tree.getInvokesStopCellEditing() && !stopEditing(tree)) { return; } if (tree.isRequestFocusEnabled()) { tree.requestFocus(); } TreePath path = getClosestPathForLocation(tree, evt.getX(), evt.getY()); if(path!=null){ Rectangle bounds = getPathBounds(tree, path); if(evt.getY() > (bounds.y + bounds.height)) { return; } if(SwingUtilities.isLeftMouseButton(evt)) checkForClickInExpandControl(path, evt.getX(), evt.getY()); if(!startEditing(path, evt)){ selectPathForEvent(path, evt); } } } } } }
Notes: The g.fillRect line in the orginal version had a bug, we used the bounds.width when we should have used tree.getWidth(). If you look at the source code for BasicTreeUI you’ll notice that the MouseListener is almost the same. In the BasicTreeUI MouseListener goes out of it’s way to test the X axis, we don’t — that simple.

As I said before, both OS X and Windows XP have the wide “hot spots”, if you want to emulate the look of Windows XP, take out the drawing parts of the code. Now the odd thing is neither the Apple-supplied native OS X LAF nor the Sun-supplied native Windows LAF behave this way despite both OSes accepting clicks across the entire row. Same with Quaqua, Alloy, and JGoodies Looks. It would seem that Java Look and Feel developers (myself included) are more concerned with the “Look” — generating cool screenshots — that with with “Feel”. In my experience, the look sales but it’s the feel of an application that helps form long-term opinions.


Hi Adam,
Thank you for your great article.
I am very concerned about getting both the look and the behaviour right in my Quaqua look and feel. I always thought there had to be an area on a JTree which does not react on clicks, in order to support rubber band selection. But now I see that rubber band selection does not exist on OS X...
If you don\'t mind, I am going to roll in your FittsMouseListener into the Quaqua L&F.
-Cheers, Werner

Werner Randelshofer · Sep 28, 05:28 PM · #

Hi Werner, feel free to use FittsMouseListener in Quaqua. I haven\'t seen rubber banding in OS X however if you switch the finder display to \"as List\" (for those without OS X it displays the folder in a tree-table like setup) you can drag to select multiple rows (like rubber banding) but without the visual rubber band feedback. The FittsMouseListener doesn\'t do this yet. I\'ll try to implement it later.

Oddly, when trying Apple\'s interface builder, I have not found a separate tree (like in iTunes -- just a tree, no header) only the tree-table. The tree in iTunes is only a single selection tree.

— Adam · Sep 28, 06:20 PM · #

Cool, thanks for your code!
Oh my. The more I look at the selection behaviors in Apple\'s Tree controls, the more differences I find. Still worse is, that in the current implementation of the Quaqua L&F, the selection behaviour differs even between JList, JTable and JTree. In a JTable, I can drag for multiple selections (as you have descibed for OS X), in a JList and a JTree, I can not.
Quaqua gets all these behaviours from the BasicLookAndFeel. Why isn\'t it consistent in there? Lots of work to do. :(

You are right about Interface Builder, there does not seem to be a pure tree, just a tree-table...

Werner Randelshofer · Sep 28, 06:41 PM · #

Hey Adam,

Great post. It never occured to me before (probably because I don\'t use JTree that much). I\'m going to put an RFE in SwingLabs to do this fix to JTree. Maybe we can see something like this roll into Swing.


— Richard · Oct 3, 02:57 PM · #

Commenting is closed for this article.