001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.util;
030
031import java.awt.Component;
032import java.awt.Container;
033import java.awt.FocusTraversalPolicy;
034import 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 */
045public 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 componentsToTraverse 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}