/* * @(#)FocusArrowListener.java * * $Date: 2012-07-03 01:10:05 -0500 (Tue, 03 Jul 2012) $ * * Copyright (c) 2011 by Jeremy Wood. * All rights reserved. * * The copyright of this software is owned by Jeremy Wood. * You may not use, copy or modify this software, except in * accordance with the license agreement you entered into with * Jeremy Wood. For details see accompanying license terms. * * This software is probably, but not necessarily, discussed here: * http://javagraphics.java.net/ * * That site should also contain the most recent official version * of this software. (See the SVN repository for more details.) */ package ale.util.colors.bric.plaf; import java.awt.Component; import java.awt.Container; import java.awt.FocusTraversalPolicy; import java.awt.Point; import java.awt.Window; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.HashSet; import java.util.Set; import javax.swing.JPanel; import javax.swing.SwingUtilities; /** * This listens for arrow keys and shifts the keyboard focus accordingly. So if you press the left arrow key, the * component to the left of the source component requests the focus. * <P> * This scans for the first available component whose <code>isFocusable()</code> method returns <code>true</code>. If no * such component is found: nothing happens. */ public class FocusArrowListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { int code = e.getKeyCode(); int dx = 0; int dy = 0; if (code == KeyEvent.VK_LEFT) { dx = -1; } else if (code == KeyEvent.VK_UP) { dy = -1; } else if (code == KeyEvent.VK_RIGHT) { dx = 1; } else if (code == KeyEvent.VK_DOWN) { dy = 1; } if ((((dx == 0) && (dy == 0)) == false) && shiftFocus(dx, dy, (Component) e.getSource())) { e.consume(); } } /** * Shifts the focus in a certain direction. * * @param dx * the amount to increment x. * @param dy * the amount to increment y. * @param src * the source to traverse from. * @return true if another component requested the focus as a result of this method. This may return false if no * suitable component was found to shift focus to. (If you press the right arrow key on the right-most * component, for example.) */ public static boolean shiftFocus(int dx, int dy, Component src) { if ((dx == 0) && (dy == 0)) { throw new IllegalArgumentException("dx (" + dx + ") and (" + dy + ") cannot both be zero"); } Set<Component> focusableComponents = getFocusableComponents(src); int x = src.getWidth() / 2; int y = src.getHeight() / 2; Window window = SwingUtilities.getWindowAncestor(src); if (window == null) { return false; } Point p = SwingUtilities.convertPoint(src, x, y, window); Component comp = null; int windowWidth = window.getWidth(); int windowHeight = window.getHeight(); while ((p.x > 0) && (p.x < windowWidth) && (p.y > 0) && (p.y < windowHeight) && ((comp == null) || (comp == src) || (comp instanceof JPanel))) { p.x += dx; p.y += dy; comp = SwingUtilities.getDeepestComponentAt(window, p.x, p.y); boolean canAcceptFocus = focusableComponents.contains(comp); if ((comp != null) && (canAcceptFocus == false)) { comp = null; } } // TODO: implement a more robust searching mechanism instead of the above // If a component is below the src, but to the left or right of the center: // it should still be detected when you press the down arrow key. if ((comp != null) && (comp != src) && (comp != window) && (!(comp instanceof JPanel))) { comp.requestFocus(); return true; } return false; } /** * Returns a set of all the components that can have the keyboard focus. * <P> * My first implementation involved of this concept simply involved asking JCompnonents if they were focusable, but * in the <code>FilledButtonTest</code> this resulted in shifting focus to the ContentPane. Although it is * technically focusable: if I used the tab key I did <i>not</i> get this result. So I studied the inner workings * for Component.transferFocus() and ended up with a method that involved calls to * <code>getFocusCycleRootAncestor()</code>, and <code>getFocusTraversalPolicy()</code>. * <P> * (Also credit goes to Werner for originally tipping me off towards looking at FocusTraversalPolicies.) * * @param currentFocusOwner * the current focus owner. * @return all the JComponents that can receive the focus. */ public static Set<Component> getFocusableComponents(Component currentFocusOwner) { HashSet<Component> set = new HashSet<Component>(); set.add(currentFocusOwner); Container rootAncestor = currentFocusOwner.getFocusCycleRootAncestor(); Component comp = currentFocusOwner; while ((rootAncestor != null) && !(rootAncestor.isShowing() && rootAncestor.isFocusable() && rootAncestor.isEnabled())) { comp = rootAncestor; rootAncestor = comp.getFocusCycleRootAncestor(); } if (rootAncestor != null) { FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); Component toFocus = policy.getComponentAfter(rootAncestor, comp); while ((toFocus != null) && (set.contains(toFocus) == false)) { set.add(toFocus); toFocus = policy.getComponentAfter(rootAncestor, toFocus); } toFocus = policy.getComponentBefore(rootAncestor, comp); while ((toFocus != null) && (set.contains(toFocus) == false)) { set.add(toFocus); toFocus = policy.getComponentBefore(rootAncestor, toFocus); } } return set; } }