001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.util;
017
018 import java.awt.Component;
019 import java.awt.KeyboardFocusManager;
020 import java.awt.Window;
021 import java.util.ArrayList;
022 import java.util.Comparator;
023 import java.util.HashMap;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Map;
027
028 import javax.swing.JComponent;
029 import javax.swing.LayoutFocusTraversalPolicy;
030
031 /**
032 * A LayoutFocusTraversalPolicy that allows for individual containers to have a
033 * custom focus order.
034 *
035 * @author oliverh
036 */
037 public class CustomizableFocusTraversalPolicy extends LayoutFocusTraversalPolicy {
038
039 private static final String FOCUS_ORDER_PROPERTY_NAME = "customFocusOrder";
040
041 /**
042 * Installs an instance of CustomizableFocusTraversalPolicy as the default
043 * focus traversal policy.
044 */
045 public static void installCustomizableFocusTraversalPolicy() {
046 KeyboardFocusManager.getCurrentKeyboardFocusManager().setDefaultFocusTraversalPolicy(
047 new CustomizableFocusTraversalPolicy());
048 }
049
050 /**
051 * Sets a custom focus traversal order for the given container. Child
052 * components for which there is no order specified will receive focus after
053 * components that do have an order specified in the standard "layout"
054 * order.
055 *
056 * @param container
057 * the container
058 * @param componentsInOrder
059 * a list of child components in the order that thay should
060 * receive focus
061 */
062 public static void customizeFocusTraversalOrder(JComponent container, List componentsInOrder) {
063 for (Iterator i = componentsInOrder.iterator(); i.hasNext();) {
064 Component comp = (Component)i.next();
065 if (comp.getParent() != container) {
066 throw new IllegalArgumentException("Component [" + comp + "] is not a child of [" + container + "].");
067 }
068 }
069 container.putClientProperty(FOCUS_ORDER_PROPERTY_NAME, createOrderMapFromList(componentsInOrder));
070 }
071
072 private static Map createOrderMapFromList(List componentsInOrder) {
073 HashMap orderMap = new HashMap(componentsInOrder.size());
074 for (int i = 0; i < componentsInOrder.size(); i++) {
075 orderMap.put(componentsInOrder.get(i), new Integer(i));
076 }
077 return orderMap;
078 }
079
080 /**
081 * Creates a new CustomizableFocusTraversalPolicy
082 */
083 public CustomizableFocusTraversalPolicy() {
084 setComparator(new CustomizableFocusTraversalComparator(getComparator()));
085 }
086
087 private static class CustomizableFocusTraversalComparator implements Comparator {
088
089 private Comparator layoutComparator;
090
091 private CustomizableFocusTraversalComparator(Comparator layoutComparator) {
092 this.layoutComparator = layoutComparator;
093 }
094
095 public int compare(Object o1, Object o2) {
096 Component comp1 = (Component)o1;
097 Component comp2 = (Component)o2;
098 if (comp1 == comp2) {
099 return 0;
100 }
101 Map order = getFocusOrder(comp1);
102 if (order != null && comp1.getParent() == comp2.getParent()) {
103 return compareSameParent(order, comp1, comp2);
104 }
105 else if (comp1.getParent() != comp2.getParent()) {
106 return compareClosestAncestor(comp1, comp2);
107 }
108 else {
109 return layoutComparator.compare(comp1, comp2);
110 }
111 }
112
113 private Map getFocusOrder(Component comp) {
114 Component parent = comp.getParent();
115 return (Map)((parent instanceof JComponent) ? ((JComponent)parent)
116 .getClientProperty(FOCUS_ORDER_PROPERTY_NAME) : null);
117 }
118
119 private int compareSameParent(Map order, Component comp1, Component comp2) {
120 Integer index1 = (Integer)order.get(comp1);
121 Integer index2 = (Integer)order.get(comp2);
122 if (index1 != null && index2 != null) {
123 return index1.intValue() - index2.intValue();
124 }
125 else if (index1 != null) {
126 return -1;
127 }
128 else if (index2 != null) {
129 return 1;
130 }
131 else {
132 return layoutComparator.compare(comp1, comp2);
133 }
134 }
135
136 public int compareClosestAncestor(Component comp1, Component comp2) {
137 List comp1Ancestors = getAncestors(comp1);
138 List comp2Ancestors = getAncestors(comp2);
139 int index1 = comp1Ancestors.size();
140 int index2 = comp2Ancestors.size();
141 while (true) {
142 if (index1 > 0) {
143 comp1 = (Component)comp1Ancestors.get(--index1);
144 }
145 else {
146 return -1;
147 }
148 if (index2 > 0) {
149 comp2 = (Component)comp2Ancestors.get(--index2);
150 }
151 else {
152 return 1;
153 }
154 if (comp1 != comp2) {
155 break;
156 }
157 }
158 return compare(comp1, comp2);
159 }
160
161 private List getAncestors(Component comp) {
162 List ancestors = new ArrayList();
163 while (comp != null) {
164 if (comp instanceof Window) {
165 break;
166 }
167 ancestors.add(comp);
168 comp = comp.getParent();
169 }
170 return ancestors;
171 }
172 }
173 }