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 }