001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    
029    package edu.wisc.ssec.mcidasv.util;
030    
031    import java.awt.Component;
032    import java.awt.Container;
033    import java.awt.FocusTraversalPolicy;
034    import java.util.Collection;
035    
036    /**
037     * Abstraction of {@link FocusTraversalPolicy} that allows for easy creation of
038     * the traversal policy.
039     * 
040     * <p>Note that the {@literal "delta"} parameter of both 
041     * {@link #cycle(Component, int)} and {@link #indexCycle(int, int)} can be any
042     * positive or negative integer. Both methods compute indices using the wonders
043     * of modular arithmetic.
044     */
045    public class FocusTraveller extends FocusTraversalPolicy {
046    
047        /** Components to traverse, stored in the desired traversal order. */
048        private final Component[] components;
049    
050        /**
051         * Creates the {@link FocusTraversalPolicy}.
052         * 
053         * @param components Components to traverse, in the desired order. 
054         * Cannot be {@code null}.
055         */
056        public FocusTraveller(final Component... componentsToTraverse) {
057            components = componentsToTraverse;
058        }
059    
060        /**
061         * Creates the {@link FocusTraversalPolicy} for the given components.
062         * 
063         * @param componentsToTraverse Components to traverse. Cannot be {@code null}.
064         */
065        public FocusTraveller(final Collection<Component> componentsToTraverse) {
066            components = componentsToTraverse.toArray(new Component[0]);
067        }
068    
069        /**
070         * Cycles through valid index values.
071         * 
072         * @param index Current index.
073         * @param delta Index of next component, relative to {@code index}.
074         * 
075         * @return Next index value.
076         */
077        private int indexCycle(final int index, final int delta) {
078            int size = components.length;
079            return (index + delta + size) % size;
080        }
081    
082        /**
083         * Cycles through components. 
084         * 
085         * @param currentComponent Cannot be {@code null}.
086         * @param delta Index of next component, relative to {@code currentComponent}.
087         * 
088         * @return The {@literal "next"} component in the traversal order.
089         */
090        private Component cycle(final Component currentComponent, final int delta) {
091            int index = -1;
092            loop: for (int i = 0; i < components.length; i++) {
093                Component component = components[i];
094                for (Component c = currentComponent; c != null; c = c.getParent()) {
095                    if (component == c) {
096                        index = i;
097                        break loop;
098                    }
099                }
100            }
101            
102            // try to find enabled component in "delta" direction
103            int initialIndex = index;
104            while (true) {
105                int newIndex = indexCycle(index, delta);
106                if (newIndex == initialIndex) {
107                    break;
108                }
109                index = newIndex;
110                
111                Component component = components[newIndex];
112                if (component.isEnabled() && component.isVisible() && component.isFocusable()) {
113                    return component;
114                }
115            }
116            // not found
117            return currentComponent;
118        }
119    
120        /**
121         * Gets the next component after {@code component}.
122         * 
123         * @param container Ignored.
124         * @param component Cannot be {@code null}.
125         * 
126         * @return Next component after {@code component}.
127         */
128        public Component getComponentAfter(final Container container, final Component component) {
129            return cycle(component, 1);
130        }
131    
132        /**
133         * Gets the component before {@code component}.
134         * 
135         * @param container Ignored.
136         * @param component Cannot be {@code null}.
137         * 
138         * @return The {@link Component} before {@code component} in traversal order.
139         */
140        public Component getComponentBefore(final Container container, final Component component) {
141            return cycle(component, -1);
142        }
143    
144        /**
145         * Gets the first component.
146         * 
147         * @param container Ignored.
148         * 
149         * @return The first {@link Component} in traversal order.
150         */
151        public Component getFirstComponent(final Container container) {
152            return components[0];
153        }
154    
155        /**
156         * Gets the last component.
157         * 
158         * @param container Ignored.
159         * 
160         * @return The last {@link Component} in traversal order.
161         */
162        public Component getLastComponent(final Container container) {
163            return components[components.length - 1];
164        }
165    
166        /**
167         * Not used. See {@link #getFirstComponent(Container)}.
168         * 
169         * @param container Ignored.
170         * 
171         * @return The first {@link Component} in traversal order.
172         */
173        public Component getDefaultComponent(final Container container) {
174            return getFirstComponent(container);
175        }
176    }