KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > SortingFocusTraversalPolicy


1 /*
2  * @(#)SortingFocusTraversalPolicy.java 1.8 06/03/16
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing;
8
9 import java.awt.Component JavaDoc;
10 import java.awt.Container JavaDoc;
11 import java.awt.Window JavaDoc;
12 import java.util.*;
13 import java.awt.FocusTraversalPolicy JavaDoc;
14 import java.util.logging.*;
15
16 /**
17  * A FocusTraversalPolicy that determines traversal order by sorting the
18  * Components of a focus traversal cycle based on a given Comparator. Portions
19  * of the Component hierarchy that are not visible and displayable will not be
20  * included.
21  * <p>
22  * By default, SortingFocusTraversalPolicy implicitly transfers focus down-
23  * cycle. That is, during normal focus traversal, the Component
24  * traversed after a focus cycle root will be the focus-cycle-root's default
25  * Component to focus. This behavior can be disabled using the
26  * <code>setImplicitDownCycleTraversal</code> method.
27  * <p>
28  * By default, methods of this class with return a Component only if it is
29  * visible, displayable, enabled, and focusable. Subclasses can modify this
30  * behavior by overriding the <code>accept</code> method.
31  * <p>
32  * This policy takes into account <a
33  * HREF="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus traversal
34  * policy providers</a>. When searching for first/last/next/previous Component,
35  * if a focus traversal policy provider is encountered, its focus traversal
36  * policy is used to perform the search operation.
37  *
38  * @author David Mendenhall
39  * @version 1.8, 03/16/06
40  *
41  * @see java.util.Comparator
42  * @since 1.4
43  */

44 public class SortingFocusTraversalPolicy
45     extends InternalFrameFocusTraversalPolicy JavaDoc
46 {
47     private Comparator<? super Component JavaDoc> comparator;
48     private boolean implicitDownCycleTraversal = true;
49
50     private Logger log = Logger.getLogger("javax.swing.SortingFocusTraversalPolicy");
51
52     /**
53      * Used by getComponentAfter and getComponentBefore for efficiency. In
54      * order to maintain compliance with the specification of
55      * FocusTraversalPolicy, if traversal wraps, we should invoke
56      * getFirstComponent or getLastComponent. These methods may be overriden in
57      * subclasses to behave in a non-generic way. However, in the generic case,
58      * these methods will simply return the first or last Components of the
59      * sorted list, respectively. Since getComponentAfter and
60      * getComponentBefore have already built the sorted list before determining
61      * that they need to invoke getFirstComponent or getLastComponent, the
62      * sorted list should be reused if possible.
63      */

64     private Container JavaDoc cachedRoot;
65     private List cachedCycle;
66
67     // Delegate our fitness test to ContainerOrder so that we only have to
68
// code the algorithm once.
69
private static final SwingContainerOrderFocusTraversalPolicy
70     fitnessTestPolicy = new SwingContainerOrderFocusTraversalPolicy();
71
72     /**
73      * Constructs a SortingFocusTraversalPolicy without a Comparator.
74      * Subclasses must set the Comparator using <code>setComparator</code>
75      * before installing this FocusTraversalPolicy on a focus cycle root or
76      * KeyboardFocusManager.
77      */

78     protected SortingFocusTraversalPolicy() {
79     }
80
81     /**
82      * Constructs a SortingFocusTraversalPolicy with the specified Comparator.
83      */

84     public SortingFocusTraversalPolicy(Comparator<? super Component JavaDoc> comparator) {
85     this.comparator = comparator;
86     }
87
88     private void enumerateAndSortCycle(Container JavaDoc focusCycleRoot,
89                        List cycle, Map defaults) {
90     List defaultRoots = null;
91
92     if (!focusCycleRoot.isShowing()) {
93         return;
94     }
95
96     enumerateCycle(focusCycleRoot, cycle);
97
98     boolean addDefaultComponents =
99         (defaults != null && getImplicitDownCycleTraversal());
100
101         if (log.isLoggable(Level.FINE)) log.fine("### Will add defaults: " + addDefaultComponents);
102
103     // Create a list of all default Components which should be added
104
// to the list
105
if (addDefaultComponents) {
106         defaultRoots = new ArrayList();
107         for (Iterator iter = cycle.iterator(); iter.hasNext(); ) {
108         Component JavaDoc comp = (Component JavaDoc)iter.next();
109         if ((comp instanceof Container JavaDoc) &&
110             ((Container JavaDoc)comp).isFocusCycleRoot())
111         {
112             defaultRoots.add(comp);
113         }
114         }
115         Collections.sort(defaultRoots, comparator);
116     }
117
118     // Sort the Components in the cycle
119
Collections.sort(cycle, comparator);
120
121     // Find all of the roots in the cycle and place their default
122
// Components after them. Note that the roots may have been removed
123
// from the list because they were unfit. In that case, insert the
124
// default Components as though the roots were still in the list.
125
if (addDefaultComponents) {
126         for (ListIterator defaultRootsIter =
127              defaultRoots.listIterator(defaultRoots.size());
128          defaultRootsIter.hasPrevious(); )
129         {
130         Container JavaDoc root = (Container JavaDoc)defaultRootsIter.previous();
131         Component JavaDoc defComp =
132             root.getFocusTraversalPolicy().getDefaultComponent(root);
133
134         if (defComp != null && defComp.isShowing()) {
135             int index = Collections.binarySearch(cycle, root,
136                              comparator);
137             if (index < 0) {
138             // If root is not in the list, then binarySearch
139
// returns (-(insertion point) - 1). defComp follows
140
// the index one less than the insertion point.
141

142             index = -index - 2;
143             }
144             
145             defaults.put(new Integer JavaDoc(index), defComp);
146         }
147         }
148     }
149     }
150
151     private void enumerateCycle(Container JavaDoc container, List cycle) {
152     if (!(container.isVisible() && container.isDisplayable())) {
153         return;
154     }
155
156     cycle.add(container);
157
158     Component JavaDoc[] components = container.getComponents();
159     for (int i = 0; i < components.length; i++) {
160         Component JavaDoc comp = components[i];
161         if ((comp instanceof Container JavaDoc)
162                 && !((Container JavaDoc)comp).isFocusTraversalPolicyProvider()
163                 && !((Container JavaDoc)comp).isFocusCycleRoot()
164                 && !((comp instanceof JComponent JavaDoc)
165                      && ((JComponent JavaDoc)comp).isManagingFocus()))
166             {
167         enumerateCycle((Container JavaDoc)comp, cycle);
168         } else {
169         cycle.add(comp);
170         }
171     }
172     }
173
174     Container JavaDoc getTopmostProvider(Container JavaDoc focusCycleRoot, Component JavaDoc aComponent) {
175         Container JavaDoc aCont = aComponent.getParent();
176         Container JavaDoc ftp = null;
177         while (aCont != focusCycleRoot && aCont != null) {
178             if (aCont.isFocusTraversalPolicyProvider()) {
179                 ftp = aCont;
180             }
181             aCont = aCont.getParent();
182         }
183         if (aCont == null) {
184             return null;
185         }
186         return ftp;
187     }
188
189     /**
190      * Returns the Component that should receive the focus after aComponent.
191      * aContainer must be a focus cycle root of aComponent or a focus traversal policy provider.
192      * <p>
193      * By default, SortingFocusTraversalPolicy implicitly transfers focus down-
194      * cycle. That is, during normal focus traversal, the Component
195      * traversed after a focus cycle root will be the focus-cycle-root's
196      * default Component to focus. This behavior can be disabled using the
197      * <code>setImplicitDownCycleTraversal</code> method.
198      * <p>
199      * If aContainer is <a HREF="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus
200      * traversal policy provider</a>, the focus is always transferred down-cycle.
201      *
202      * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider
203      * @param aComponent a (possibly indirect) child of aContainer, or
204      * aContainer itself
205      * @return the Component that should receive the focus after aComponent, or
206      * null if no suitable Component can be found
207      * @throws IllegalArgumentException if aContainer is not a focus cycle
208      * root of aComponent or a focus traversal policy provider, or if either aContainer or
209      * aComponent is null
210      */

211     public Component JavaDoc getComponentAfter(Container JavaDoc aContainer,
212                        Component JavaDoc aComponent) {
213         if (log.isLoggable(Level.FINE)) log.fine("### Searching in " + aContainer.getName() + " for component after " + aComponent.getName());
214
215         if (aContainer == null || aComponent == null) {
216             throw new IllegalArgumentException JavaDoc("aContainer and aComponent cannot be null");
217         }
218         if (!aContainer.isFocusTraversalPolicyProvider() && !aContainer.isFocusCycleRoot()) {
219             throw new IllegalArgumentException JavaDoc("aContainer should be focus cycle root or focus traversal policy provider");
220         } else if (aContainer.isFocusCycleRoot() && !aComponent.isFocusCycleRoot(aContainer)) {
221             throw new IllegalArgumentException JavaDoc("aContainer is not a focus cycle root of aComponent");
222         }
223
224         // See if the component is inside of policy provider
225
Container JavaDoc ftp = getTopmostProvider(aContainer, aComponent);
226         if (ftp != null) {
227             if (log.isLoggable(Level.FINE)) log.fine("### Asking FTP " + ftp.getName() + " for component after " + aComponent.getName());
228             // FTP knows how to find component after the given. We don't.
229
FocusTraversalPolicy JavaDoc policy = ftp.getFocusTraversalPolicy();
230             Component JavaDoc retval = policy.getComponentAfter(ftp, aComponent);
231             if (retval == policy.getFirstComponent(ftp)) {
232                 retval = null;
233             }
234             
235             if (retval != null) {
236                 if (log.isLoggable(Level.FINE)) log.fine("### FTP returned " + retval.getName());
237                 return retval;
238         }
239             aComponent = ftp;
240         }
241
242     List cycle = new ArrayList();
243     Map defaults = new HashMap();
244         enumerateAndSortCycle(aContainer, cycle, defaults);
245
246     int index;
247     try {
248         index = Collections.binarySearch(cycle, aComponent, comparator);
249     } catch (ClassCastException JavaDoc e) {
250             if (log.isLoggable(Level.FINE)) log.fine("### Didn't find component " + aComponent.getName() + " in a cycle " + aContainer.getName());
251             return getFirstComponent(aContainer);
252     }
253
254     if (index < 0) {
255         // Fix for 5070991.
256
// A workaround for a transitivity problem caused by ROW_TOLERANCE,
257
// because of that the component may be missed in the binary search.
258
// Try to search it again directly.
259
int i = cycle.indexOf(aComponent);
260             if (i >= 0) {
261                 index = i;
262             } else {
263                 // If we're not in the cycle, then binarySearch returns
264
// (-(insertion point) - 1). The next element is our insertion
265
// point.
266

267                 index = -index - 2;
268             }
269     }
270
271     Component JavaDoc defComp = (Component JavaDoc)defaults.get(new Integer JavaDoc(index));
272     if (defComp != null) {
273         return defComp;
274     }
275
276         do {
277     index++;
278
279     if (index >= cycle.size()) {
280                 if (aContainer.isFocusCycleRoot()) {
281                     this.cachedRoot = aContainer;
282         this.cachedCycle = cycle;
283
284                     Component JavaDoc retval = getFirstComponent(aContainer);
285
286         this.cachedRoot = null;
287         this.cachedCycle = null;
288
289         return retval;
290     } else {
291                     return null;
292                 }
293             } else {
294                 Component JavaDoc comp = (Component JavaDoc)cycle.get(index);
295                 if (accept(comp)) {
296                     return comp;
297                 } else if (comp instanceof Container JavaDoc && ((Container JavaDoc)comp).isFocusTraversalPolicyProvider()) {
298                     return ((Container JavaDoc)comp).getFocusTraversalPolicy().getDefaultComponent((Container JavaDoc)comp);
299                 }
300     }
301         } while (true);
302     }
303
304     /**
305      * Returns the Component that should receive the focus before aComponent.
306      * aContainer must be a focus cycle root of aComponent or a focus traversal policy provider.
307      * <p>
308      * By default, SortingFocusTraversalPolicy implicitly transfers focus down-
309      * cycle. That is, during normal focus traversal, the Component
310      * traversed after a focus cycle root will be the focus-cycle-root's
311      * default Component to focus. This behavior can be disabled using the
312      * <code>setImplicitDownCycleTraversal</code> method.
313      * <p>
314      * If aContainer is <a HREF="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus
315      * traversal policy provider</a>, the focus is always transferred down-cycle.
316      *
317      * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider
318      * @param aComponent a (possibly indirect) child of aContainer, or
319      * aContainer itself
320      * @return the Component that should receive the focus before aComponent,
321      * or null if no suitable Component can be found
322      * @throws IllegalArgumentException if aContainer is not a focus cycle
323      * root of aComponent or a focus traversal policy provider, or if either aContainer or
324      * aComponent is null
325      */

326     public Component JavaDoc getComponentBefore(Container JavaDoc aContainer,
327                     Component JavaDoc aComponent) {
328         if (aContainer == null || aComponent == null) {
329             throw new IllegalArgumentException JavaDoc("aContainer and aComponent cannot be null");
330         }
331         if (!aContainer.isFocusTraversalPolicyProvider() && !aContainer.isFocusCycleRoot()) {
332             throw new IllegalArgumentException JavaDoc("aContainer should be focus cycle root or focus traversal policy provider");
333         } else if (aContainer.isFocusCycleRoot() && !aComponent.isFocusCycleRoot(aContainer)) {
334             throw new IllegalArgumentException JavaDoc("aContainer is not a focus cycle root of aComponent");
335         }
336
337         // See if the component is inside of policy provider
338
Container JavaDoc ftp = getTopmostProvider(aContainer, aComponent);
339         if (ftp != null) {
340             if (log.isLoggable(Level.FINE)) log.fine("### Asking FTP " + ftp.getName() + " for component after " + aComponent.getName());
341             // FTP knows how to find component after the given. We don't.
342
FocusTraversalPolicy JavaDoc policy = ftp.getFocusTraversalPolicy();
343             Component JavaDoc retval = policy.getComponentBefore(ftp, aComponent);
344             if (retval == policy.getLastComponent(ftp)) {
345                 retval = null;
346             }
347             if (retval != null) {
348                 if (log.isLoggable(Level.FINE)) log.fine("### FTP returned " + retval.getName());
349                 return retval;
350         }
351             aComponent = ftp;
352         }
353
354
355     List cycle = new ArrayList();
356     Map defaults = new HashMap();
357         enumerateAndSortCycle(aContainer, cycle, defaults);
358
359         if (log.isLoggable(Level.FINE)) log.fine("### Cycle is " + cycle + ", component is " + aComponent);
360
361     int index;
362     try {
363         index = Collections.binarySearch(cycle, aComponent, comparator);
364     } catch (ClassCastException JavaDoc e) {
365             return getLastComponent(aContainer);
366     }
367
368     if (index < 0) {
369         // If we're not in the cycle, then binarySearch returns
370
// (-(insertion point) - 1). The previous element is our insertion
371
// point - 1.
372

373             index = -index - 2;
374     } else {
375             index--;
376         }
377
378         if (log.isLoggable(Level.FINE)) log.fine("### Index is " + index);
379
380         if (index >= 0) {
381             Component JavaDoc defComp = (Component JavaDoc)defaults.get(new Integer JavaDoc(index));
382             if (defComp != null && cycle.get(index) != aContainer) {
383                 if (log.isLoggable(Level.FINE)) log.fine("### Returning default " + defComp.getName() + " at " + index);
384                 return defComp;
385             }
386         }
387
388         do {
389     if (index < 0) {
390                 this.cachedRoot = aContainer;
391         this.cachedCycle = cycle;
392
393                 Component JavaDoc retval = getLastComponent(aContainer);
394
395         this.cachedRoot = null;
396         this.cachedCycle = null;
397
398         return retval;
399     } else {
400                 Component JavaDoc comp = (Component JavaDoc)cycle.get(index);
401                 if (accept(comp)) {
402                     return comp;
403                 } else if (comp instanceof Container JavaDoc && ((Container JavaDoc)comp).isFocusTraversalPolicyProvider()) {
404                     return ((Container JavaDoc)comp).getFocusTraversalPolicy().getLastComponent((Container JavaDoc)comp);
405         }
406     }
407             index--;
408         } while (true);
409     }
410
411     /**
412      * Returns the first Component in the traversal cycle. This method is used
413      * to determine the next Component to focus when traversal wraps in the
414      * forward direction.
415      *
416      * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider whose
417      * first Component is to be returned
418      * @return the first Component in the traversal cycle of aContainer,
419      * or null if no suitable Component can be found
420      * @throws IllegalArgumentException if aContainer is null
421      */

422     public Component JavaDoc getFirstComponent(Container JavaDoc aContainer) {
423     List cycle;
424
425         if (log.isLoggable(Level.FINE)) log.fine("### Getting first component in " + aContainer.getName());
426         if (aContainer == null) {
427             throw new IllegalArgumentException JavaDoc("aContainer cannot be null");
428     }
429
430         if (this.cachedRoot == aContainer) {
431         cycle = this.cachedCycle;
432     } else {
433         cycle = new ArrayList();
434             enumerateAndSortCycle(aContainer, cycle, null);
435     }
436
437     int size = cycle.size();
438     if (size == 0) {
439         return null;
440     }
441
442         for (int i= 0; i < cycle.size(); i++) {
443             Component JavaDoc comp = (Component JavaDoc)cycle.get(i);
444             if (accept(comp)) {
445                 return comp;
446             } else if (comp instanceof Container JavaDoc && !(comp == aContainer) && ((Container JavaDoc)comp).isFocusTraversalPolicyProvider()) {
447                 return ((Container JavaDoc)comp).getFocusTraversalPolicy().getDefaultComponent((Container JavaDoc)comp);
448             }
449         }
450         return null;
451     }
452
453     /**
454      * Returns the last Component in the traversal cycle. This method is used
455      * to determine the next Component to focus when traversal wraps in the
456      * reverse direction.
457      *
458      * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider whose
459      * last Component is to be returned
460      * @return the last Component in the traversal cycle of aContainer,
461      * or null if no suitable Component can be found
462      * @throws IllegalArgumentException if aContainer is null
463      */

464     public Component JavaDoc getLastComponent(Container JavaDoc aContainer) {
465     List cycle;
466         if (log.isLoggable(Level.FINE)) log.fine("### Getting last component in " + aContainer.getName());
467
468         if (aContainer == null) {
469             throw new IllegalArgumentException JavaDoc("aContainer cannot be null");
470     }
471
472         if (this.cachedRoot == aContainer) {
473         cycle = this.cachedCycle;
474     } else {
475         cycle = new ArrayList();
476             enumerateAndSortCycle(aContainer, cycle, null);
477     }
478
479     int size = cycle.size();
480     if (size == 0) {
481             if (log.isLoggable(Level.FINE)) log.fine("### Cycle is empty");
482         return null;
483     }
484         if (log.isLoggable(Level.FINE)) log.fine("### Cycle is " + cycle);
485
486         for (int i= cycle.size()-1; i >= 0; i--) {
487             Component JavaDoc comp = (Component JavaDoc)cycle.get(i);
488             if (accept(comp)) {
489                 return comp;
490             } else if (comp instanceof Container JavaDoc && !(comp == aContainer) && ((Container JavaDoc)comp).isFocusTraversalPolicyProvider()) {
491                 return ((Container JavaDoc)comp).getFocusTraversalPolicy().getLastComponent((Container JavaDoc)comp);
492             }
493         }
494         return null;
495     }
496
497     /**
498      * Returns the default Component to focus. This Component will be the first
499      * to receive focus when traversing down into a new focus traversal cycle
500      * rooted at aContainer. The default implementation of this method
501      * returns the same Component as <code>getFirstComponent</code>.
502      *
503      * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider whose
504      * default Component is to be returned
505      * @return the default Component in the traversal cycle of aContainer,
506      * or null if no suitable Component can be found
507      * @see #getFirstComponent
508      * @throws IllegalArgumentException if aContainer is null
509      */

510     public Component JavaDoc getDefaultComponent(Container JavaDoc aContainer) {
511         return getFirstComponent(aContainer);
512     }
513
514     /**
515      * Sets whether this SortingFocusTraversalPolicy transfers focus down-cycle
516      * implicitly. If <code>true</code>, during normal focus traversal,
517      * the Component traversed after a focus cycle root will be the focus-
518      * cycle-root's default Component to focus. If <code>false</code>, the
519      * next Component in the focus traversal cycle rooted at the specified
520      * focus cycle root will be traversed instead. The default value for this
521      * property is <code>true</code>.
522      *
523      * @param implicitDownCycleTraversal whether this
524      * SortingFocusTraversalPolicy transfers focus down-cycle implicitly
525      * @see #getImplicitDownCycleTraversal
526      * @see #getFirstComponent
527      */

528     public void setImplicitDownCycleTraversal(boolean
529                           implicitDownCycleTraversal) {
530     this.implicitDownCycleTraversal = implicitDownCycleTraversal;
531     }
532
533     /**
534      * Returns whether this SortingFocusTraversalPolicy transfers focus down-
535      * cycle implicitly. If <code>true</code>, during normal focus
536      * traversal, the Component traversed after a focus cycle root will be the
537      * focus-cycle-root's default Component to focus. If <code>false</code>,
538      * the next Component in the focus traversal cycle rooted at the specified
539      * focus cycle root will be traversed instead.
540      *
541      * @return whether this SortingFocusTraversalPolicy transfers focus down-
542      * cycle implicitly
543      * @see #setImplicitDownCycleTraversal
544      * @see #getFirstComponent
545      */

546     public boolean getImplicitDownCycleTraversal() {
547     return implicitDownCycleTraversal;
548     }
549
550     /**
551      * Sets the Comparator which will be used to sort the Components in a
552      * focus traversal cycle.
553      *
554      * @param comparator the Comparator which will be used for sorting
555      */

556     protected void setComparator(Comparator<? super Component JavaDoc> comparator) {
557     this.comparator = comparator;
558     }
559
560     /**
561      * Returns the Comparator which will be used to sort the Components in a
562      * focus traversal cycle.
563      *
564      * @return the Comparator which will be used for sorting
565      */

566     protected Comparator<? super Component JavaDoc> getComparator() {
567     return comparator;
568     }
569
570     /**
571      * Determines whether a Component is an acceptable choice as the new
572      * focus owner. By default, this method will accept a Component if and
573      * only if it is visible, displayable, enabled, and focusable.
574      *
575      * @param aComponent the Component whose fitness as a focus owner is to
576      * be tested
577      * @return <code>true</code> if aComponent is visible, displayable,
578      * enabled, and focusable; <code>false</code> otherwise
579      */

580     protected boolean accept(Component JavaDoc aComponent) {
581     return fitnessTestPolicy.accept(aComponent);
582     }
583 }
584
585 // Create our own subclass and change accept to public so that we can call
586
// accept.
587
class SwingContainerOrderFocusTraversalPolicy
588     extends java.awt.ContainerOrderFocusTraversalPolicy JavaDoc
589 {
590     public boolean accept(Component JavaDoc aComponent) {
591     return super.accept(aComponent);
592     }
593 }
594
Popular Tags