/*
* @(#)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 com.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) //this would result in an infinite loop
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;
}
}