// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>. // // TotalRecall is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 only. // // TotalRecall is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with TotalRecall. If not, see <http://www.gnu.org/licenses/>. package components; import java.awt.Component; import java.awt.Container; import java.awt.FocusTraversalPolicy; import java.awt.Window; import util.LoopIterator; import components.annotations.AnnotationTable; import components.audiofiles.AudioFileList; import components.wordpool.WordpoolList; import components.wordpool.WordpoolTextField; /** * A custom <code>FocusTraversalPolicy</code> for this program, including documentation on general focus guidelines of the program. * * <p>To speed up the annotation process we generally want users to be able to use the program using only the keyboard. * One exception is the adding of audio files. For that you need to drag files onto the program with a mouse, or use * the mouse on the file chooser. * * <p>Any component in <code>MyFrame</code> that can be clicked by the user MUST handle focus passing. * The simplest way to do this is to add an anonymous mouse listener to a clickable component that calls * {@link javax.swing.JComponent#requestFocusInWindow()} on whatever component it wants to pass focus to. * Focus should always be in one of (1) <code>MyFrame</code>, (2) <code>VolumeSliderDisplay.VolumeSlider</code>, * (3) <code>AudioFileList</code>, (4) <code>WordpoolTextField</code>, (5) <code>WordpoolList</code>, (6) <code>AnnotationTable</code>. * Other <code>JComponents</code> should choose the above components to pass focus to when clicked on. * For example, the <code>ControlPanel</code> gives focus to <code>MyFrame</code>, and the mute button gives focus to the volume slider. * * <p>Focusable components are traversed in the order given above, looping back from the last component to the first. * * <p>Please keep the spreadsheet in /dev updated with changes to the focus subsystem. * * @author Yuvi Masory * */ public class MyFocusTraversalPolicy extends FocusTraversalPolicy { private static final String genericFailureMessage = "can't find a focus-appropriate component to give focus to"; //these are components that can take focus, in the order of focus traversal desired //must have at least one element to avoid ArrayIndexOutOfBoundsException private static final Component[] focusLoop = new Component[]{ MyFrame.getInstance(), AudioFileList.getFocusTraversalReference(), WordpoolTextField.getFocusTraversalReference(), WordpoolList.getFocusTraversalReference(), AnnotationTable.getFocusTraversalReference(), DoneButton.getInstance() }; /** * Returns the next component in the focus traversal loop. * {@inheritDoc} */ @Override public Component getComponentAfter(Container aContainer, Component aComponent) { return getNextComponent(aComponent, true); } /** * Returns the previous component in the focus traversal loop. * {@inheritDoc} */ @Override public Component getComponentBefore(Container aContainer, Component aComponent) { return getNextComponent(aComponent, false); } /** * Returns the first component in the focus traversal list. * {@inheritDoc} */ @Override public Component getDefaultComponent(Container aContainer) { return focusLoop[0]; } /** * Returns the first component in the focus traversal list. * {@inheritDoc} */ @Override public Component getInitialComponent(Window window) { return focusLoop[0]; } /** * Returns the first component in the focus traversal list. * {@inheritDoc} */ @Override public Component getFirstComponent(Container aContainer) { return focusLoop[0]; } /** * Returns the last component in the focus traversal list. * {@inheritDoc} */ @Override public Component getLastComponent(Container aContainer) { return focusLoop[focusLoop.length - 1]; } /** * Handles the job of finding the next/previous component in the loop by using a {@link util.LoopIterator}. * Makes sure that the next component in the focus traversal cycle is actually eligible for focus (i.e., enabled, * visible, focusable). * * @param aComponent The base component whose successor/predecessor is to be found * @param forward <code>true</code> iff the direction of traversal is forward * @return The next focus-eligible component in the provided direction */ private Component getNextComponent(Component aComponent, boolean forward) { int componentIndex = -1; for(int i = 0; i < focusLoop.length; i++) { Component fc = focusLoop[i]; if(fc != aComponent) { continue; } else { componentIndex = i; break; } } if(componentIndex < 0) { System.err.println("can't find the next focus component because I don't recognize the current one: " + aComponent); } LoopIterator<Component> li = new LoopIterator<Component>(focusLoop, componentIndex, forward); if(li.hasNext()) { li.next(); while(li.hasNext()) { Component c = li.next(); //these are the three conditions for a component to be eligible for focus if(c.isEnabled() && c.isVisible() && c.isFocusable()) { return c; } } System.err.println(genericFailureMessage); return null; } else { System.err.println(genericFailureMessage); return null; } } }