JDK-4514858 : keyboard access to split panes, especially hierarchical splits
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.3.1
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_7
  • CPU: sparc
  • Submitted: 2001-10-15
  • Updated: 2002-08-12
  • Resolved: 2002-03-09
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.4.1 hopperFixed
Related Reports
Relates :  
Description
I'm finding lots of problems with keyboard-only access to split panes:

- If I have hierarchical split panes, I cannot figure out how to get F8 to get focus to the parent splitter bar.  and I cannot figure out how to get F6 to put focus into the "aunt" (sibling of parent) pane.

- If I put focus on the splitter bar with F8, I can't figure out how to get focus back to where it had been before.

- With the mouse, the user can use those little arrows in the splitter bar to jump the splitter-bar all the way to one side, and then jump back to where the bar had been.  With the keyboard, Home and End jump the bar to one side, but there doesn't appear to be any easy way to move the bar back to where it had been.

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: hopper FIXED IN: hopper INTEGRATED IN: hopper VERIFIED IN: hopper-beta
24-08-2004

WORK AROUND A lot of tabbing through the tab order. Ugh. --- You could also redefine F6 to suit your needs, or other bindings for quicking navigation.
24-08-2004

SUGGESTED FIX Name: apR10133 Date: 12/04/2001 ------- BasicSplitPaneUI.java ------- *** /tmp/sccs.s6aawh Mon Dec 3 11:59:12 2001 --- BasicSplitPaneUI.java Thu Nov 29 20:27:45 2001 *************** *** 84,89 **** --- 84,102 ---- /** + * Keys to use for forward focus traversal when the JComponent is + * managing focus. + */ + private static Set managingFocusForwardTraversalKeys; + + /** + * Keys to use for backward focus traversal when the JComponent is + * managing focus. + */ + private static Set managingFocusBackwardTraversalKeys; + + + /** * The size of the divider while the dragging session is valid. */ protected int dividerSize; *************** *** 303,308 **** --- 316,338 ---- } else { setNonContinuousLayoutDivider(nonContinuousLayoutDivider, true); } + + // focus forward traversal key + if (managingFocusForwardTraversalKeys==null) { + managingFocusForwardTraversalKeys = new TreeSet(); + managingFocusForwardTraversalKeys.add( + KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); + } + splitPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, + managingFocusForwardTraversalKeys); + // focus backward traversal key + if (managingFocusBackwardTraversalKeys==null) { + managingFocusBackwardTraversalKeys = new TreeSet(); + managingFocusBackwardTraversalKeys.add( + KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK)); + } + splitPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, + managingFocusBackwardTraversalKeys); } *************** *** 364,369 **** --- 394,401 ---- map.put("selectMax", new KeyboardEndAction()); map.put("startResize", new KeyboardResizeToggleAction()); map.put("toggleFocus", new ToggleSideFocusAction()); + map.put("focusOutForward", new MoveFocusOutAction(1)); + map.put("focusOutBackward", new MoveFocusOutAction(-1)); return map; } *************** *** 407,412 **** --- 439,449 ---- nonContinuousLayoutDivider = null; setNonContinuousLayoutDivider(null); + + // sets the focus forward and backward traversal keys to null + // to restore the defaults + splitPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null); + splitPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null); } *************** *** 822,829 **** public void actionPerformed(ActionEvent ev) { if (!dividerKeyboardResize) { splitPane.requestFocus(); - dividerKeyboardResize = true; - splitPane.repaint(); } } } --- 859,864 ---- *************** *** 835,842 **** BasicSplitPaneUI ui = (BasicSplitPaneUI)splitPane.getUI(); if (!ui.dividerKeyboardResize) { splitPane.requestFocus(); ! ui.dividerKeyboardResize = true; ! splitPane.repaint(); } } } --- 870,881 ---- BasicSplitPaneUI ui = (BasicSplitPaneUI)splitPane.getUI(); if (!ui.dividerKeyboardResize) { splitPane.requestFocus(); ! } else { ! JSplitPane parentSplitPane = ! (JSplitPane)SwingUtilities.getAncestorOfClass(JSplitPane.class, splitPane); ! if (parentSplitPane!=null) { ! parentSplitPane.requestFocus(); ! } } } } *************** *** 850,893 **** static class ToggleSideFocusAction extends AbstractAction { public void actionPerformed(ActionEvent ae) { JSplitPane splitPane = (JSplitPane)ae.getSource(); ! // Determine which side currently has focus. If there is only ! // one component, don't change the focus. ! Component left = splitPane.getLeftComponent(); ! Component right = splitPane.getRightComponent(); ! Component focus = SwingUtilities.findFocusOwner(left); ! Component focusOn; ! if (focus == null) { ! focusOn = SwingUtilities.findFocusOwner(right); ! if (focusOn != null) { ! if (left != null) { ! focusOn = left; ! } ! else { ! focusOn = null; ! } } ! else if (left != null) { ! focusOn = left; } ! else if (right != null) { ! focusOn = right; } } ! else if (right != null) { ! focusOn = right; } ! else { ! focusOn = left; ! } ! if (focusOn != null) { ! // Found the component to request focus on. ! focusOn.requestFocus(); ! } } } /** * Returns the divider between the top Components. */ --- 889,991 ---- static class ToggleSideFocusAction extends AbstractAction { public void actionPerformed(ActionEvent ae) { JSplitPane splitPane = (JSplitPane)ae.getSource(); ! Component left = splitPane.getLeftComponent(); ! Component right = splitPane.getRightComponent(); ! KeyboardFocusManager manager = ! KeyboardFocusManager.getCurrentKeyboardFocusManager(); ! Component focus = manager.getFocusOwner(); ! Component focusOn = getNextSide(splitPane, focus); ! if (focusOn != null) { ! // don't change the focus if the new focused component belongs ! // to the same splitpane and the same side ! if ( focus!=null && ! ( (SwingUtilities.isDescendingFrom(focus, left) && ! SwingUtilities.isDescendingFrom(focusOn, left)) || ! (SwingUtilities.isDescendingFrom(focus, right) && ! SwingUtilities.isDescendingFrom(focusOn, right)) ) ) { ! return; } ! focusOn.requestFocus(); ! } ! } ! ! private Component getNextSide(JSplitPane splitPane, Component focus) { ! Component left = splitPane.getLeftComponent(); ! Component right = splitPane.getRightComponent(); ! Component next = null; ! if (focus!=null && SwingUtilities.isDescendingFrom(focus, left) && ! right!=null) { ! next = getFirstAvailableComponent(right); ! if (next != null) { ! return next; } ! } ! JSplitPane parentSplitPane = (JSplitPane)SwingUtilities.getAncestorOfClass(JSplitPane.class, splitPane); ! if (parentSplitPane!=null) { ! // focus next side of the parent split pane ! next = getNextSide(parentSplitPane, focus); ! } else { ! next = getFirstAvailableComponent(left); ! if (next == null) { ! next = getFirstAvailableComponent(right); } } ! return next; ! } ! ! private Component getFirstAvailableComponent(Component c) { ! if (c!=null && c instanceof JSplitPane) { ! JSplitPane sp = (JSplitPane)c; ! Component left = getFirstAvailableComponent(sp.getLeftComponent()); ! if (left != null) { ! c = left; ! } else { ! c = getFirstAvailableComponent(sp.getRightComponent()); ! } } ! return c; } } + /** + * Action that will move focus out of the splitpane to the next + * component (if present) if direction > 0 or to the previous + * component (if present) if direction < 0 + */ + static class MoveFocusOutAction extends AbstractAction { + private int direction; + + public MoveFocusOutAction(int newDirection) { + direction = newDirection; + } + + public void actionPerformed(ActionEvent ae) { + JSplitPane splitPane = (JSplitPane)ae.getSource(); + Container rootAncestor = splitPane.getFocusCycleRootAncestor(); + FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); + Component focusOn = (direction > 0) ? + policy.getComponentAfter(rootAncestor, splitPane) : + policy.getComponentBefore(rootAncestor, splitPane); + HashSet focusFrom = new HashSet(); + if (splitPane.isAncestorOf(focusOn)) { + do { + focusFrom.add(focusOn); + rootAncestor = focusOn.getFocusCycleRootAncestor(); + policy = rootAncestor.getFocusTraversalPolicy(); + focusOn = (direction > 0) ? + policy.getComponentAfter(rootAncestor, focusOn) : + policy.getComponentBefore(rootAncestor, focusOn); + } while (splitPane.isAncestorOf(focusOn) && + !focusFrom.contains(focusOn)); + } + if ( focusOn!=null && !splitPane.isAncestorOf(focusOn) ) { + focusOn.requestFocus(); + } + } + } + /** * Returns the divider between the top Components. */ ------- BasicLookAndFeel.java ------- *** /tmp/sccs.iha4uR Fri Nov 16 11:18:02 2001 --- BasicLookAndFeel.java Fri Nov 16 11:17:39 2001 *************** *** 1066,1072 **** "HOME", "selectMin", "END", "selectMax", "F8", "startResize", ! "F6", "toggleFocus" }), // *** TabbedPane --- 1066,1074 ---- "HOME", "selectMin", "END", "selectMax", "F8", "startResize", ! "F6", "toggleFocus", ! "ctrl TAB", "focusOutForward", ! "ctrl shift TAB", "focusOutBackward" }), // *** TabbedPane ------- MetalLookAndFeel.java ------- *** /tmp/sccs.0WaGMU Wed Nov 14 22:19:58 2001 --- MetalLookAndFeel.java Wed Nov 14 22:17:26 2001 *************** *** 1080,1086 **** "HOME", "selectMin", "END", "selectMax", "F8", "startResize", ! "F6", "toggleFocus" }), // Tree --- 1080,1088 ---- "HOME", "selectMin", "END", "selectMax", "F8", "startResize", ! "F6", "toggleFocus", ! "ctrl TAB", "focusOutForward", ! "ctrl shift TAB", "focusOutBackward" }), // Tree and the similar fix for Motif and Windows LAFs. ###@###.### ======================================================================
24-08-2004

EVALUATION Unfortunately it would appear that the bindings are not tuned toward nested split panes. The java look and feel bindings are defined as: Tab, F6 - Navigates between split panes and gives focus to last element that had focus Tab is defined in the focus spec, and splitpane doesn't change this behavior, so this defintion should be updated to refer to the focus specification on tab behavior. Consider when you first click in a splitpane and the other component hasn't yet had focus, should f6 do nothing? Or what if you click from a different window that had focus into a splitpane, should f6 transfer focus back to that window, outside of the splitpane? For these reasons, F6 is currently implemented to give focus to the opposite component in the splitpane that doesn't have focus. As you could imagine, this is a problem in that if you have nested split panes focus can never get out of the splitpane that currently has focus. Perhaps a better algorithm would be: void focusNextSide() { if (focusInFirstComponent()) { transferFocusToLastComponent(); } else if (hasParentSplitPane()) { parentSplitPane.focusNextSide(); } else { transferFocusToFirstComponent(); } } In words: Transfer focus to the bottom/right component of the splitpane. If the bottom/right Component already has focus, and the splitpane is contained within another splitpane, the parent splitpane should process the request in the same manner. If there are no more parent split panes, then transfer focus to left/top. This will not give splitpanes that only contain splitpanes focus, but that shouldn't be a problem as as long as they have a child that is focusable one of them will get focus. I've used left/top here, but that would obviously depend upon component orientation. This would then allow the user to hit F6 to cycle between all the components of the splitpane, much as they would a focus cycle. In essense F6 would behave like tab, except for splitpanes with components that contain other components, in which it would allow you to skip over some of these components. F8 - Gives focus to splitter bar This is a problem in that it doesn't indicate what should happen if the splitter bar already has focus. We should give focus to any parent splitter bars if a splitter bar already has focus so that you can resize nested split panes with the keyboard. I'm going to email Lynn and see if these changes make sense. ###@###.### 2001-10-15
15-10-2001