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    }