1 /*
2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 
27 package javax.swing.plaf.basic;
28 
29 
30 import sun.awt.AWTAccessor;
31 import sun.awt.AWTAccessor.ComponentAccessor;
32 import sun.swing.DefaultLookup;
33 import sun.swing.UIAction;
34 import javax.swing.*;
35 import javax.swing.border.Border;
36 import java.awt.*;
37 import java.awt.event.*;
38 import java.awt.peer.ComponentPeer;
39 import java.awt.peer.LightweightPeer;
40 import java.beans.*;
41 import java.util.*;
42 import javax.swing.plaf.SplitPaneUI;
43 import javax.swing.plaf.ComponentUI;
44 import javax.swing.plaf.UIResource;
45 import sun.swing.SwingUtilities2;
46 
47 
48 /**
49  * A Basic L&F implementation of the SplitPaneUI.
50  *
51  * @author Scott Violet
52  * @author Steve Wilson
53  * @author Ralph Kar
54  */
55 public class BasicSplitPaneUI extends SplitPaneUI
56 {
57     /**
58      * The divider used for non-continuous layout is added to the split pane
59      * with this object.
60      */
61     protected static final String NON_CONTINUOUS_DIVIDER =
62         "nonContinuousDivider";
63 
64 
65     /**
66      * How far (relative) the divider does move when it is moved around by
67      * the cursor keys on the keyboard.
68      */
69     protected static int KEYBOARD_DIVIDER_MOVE_OFFSET = 3;
70 
71 
72     /**
73      * JSplitPane instance this instance is providing
74      * the look and feel for.
75      */
76     protected JSplitPane splitPane;
77 
78 
79     /**
80      * LayoutManager that is created and placed into the split pane.
81      */
82     protected BasicHorizontalLayoutManager layoutManager;
83 
84 
85     /**
86      * Instance of the divider for this JSplitPane.
87      */
88     protected BasicSplitPaneDivider divider;
89 
90 
91     /**
92      * Instance of the PropertyChangeListener for this JSplitPane.
93      */
94     protected PropertyChangeListener propertyChangeListener;
95 
96 
97     /**
98      * Instance of the FocusListener for this JSplitPane.
99      */
100     protected FocusListener focusListener;
101 
102     private Handler handler;
103 
104 
105     /**
106      * Keys to use for forward focus traversal when the JComponent is
107      * managing focus.
108      */
109     private Set<KeyStroke> managingFocusForwardTraversalKeys;
110 
111     /**
112      * Keys to use for backward focus traversal when the JComponent is
113      * managing focus.
114      */
115     private Set<KeyStroke> managingFocusBackwardTraversalKeys;
116 
117 
118     /**
119      * The size of the divider while the dragging session is valid.
120      */
121     protected int dividerSize;
122 
123 
124     /**
125      * Instance for the shadow of the divider when non continuous layout
126      * is being used.
127      */
128     protected Component nonContinuousLayoutDivider;
129 
130 
131     /**
132      * Set to true in startDragging if any of the children
133      * (not including the nonContinuousLayoutDivider) are heavy weights.
134      */
135     protected boolean draggingHW;
136 
137 
138     /**
139      * Location of the divider when the dragging session began.
140      */
141     protected int beginDragDividerLocation;
142 
143 
144     /**
145      * As of Java 2 platform v1.3 this previously undocumented field is no
146      * longer used.
147      * Key bindings are now defined by the LookAndFeel, please refer to
148      * the key bindings specification for further details.
149      *
150      * @deprecated As of Java 2 platform v1.3.
151      */
152     @Deprecated
153     protected KeyStroke upKey;
154     /**
155      * As of Java 2 platform v1.3 this previously undocumented field is no
156      * longer used.
157      * Key bindings are now defined by the LookAndFeel, please refer to
158      * the key bindings specification for further details.
159      *
160      * @deprecated As of Java 2 platform v1.3.
161      */
162     @Deprecated
163     protected KeyStroke downKey;
164     /**
165      * As of Java 2 platform v1.3 this previously undocumented field is no
166      * longer used.
167      * Key bindings are now defined by the LookAndFeel, please refer to
168      * the key bindings specification for further details.
169      *
170      * @deprecated As of Java 2 platform v1.3.
171      */
172     @Deprecated
173     protected KeyStroke leftKey;
174     /**
175      * As of Java 2 platform v1.3 this previously undocumented field is no
176      * longer used.
177      * Key bindings are now defined by the LookAndFeel, please refer to
178      * the key bindings specification for further details.
179      *
180      * @deprecated As of Java 2 platform v1.3.
181      */
182     @Deprecated
183     protected KeyStroke rightKey;
184     /**
185      * As of Java 2 platform v1.3 this previously undocumented field is no
186      * longer used.
187      * Key bindings are now defined by the LookAndFeel, please refer to
188      * the key bindings specification for further details.
189      *
190      * @deprecated As of Java 2 platform v1.3.
191      */
192     @Deprecated
193     protected KeyStroke homeKey;
194     /**
195      * As of Java 2 platform v1.3 this previously undocumented field is no
196      * longer used.
197      * Key bindings are now defined by the LookAndFeel, please refer to
198      * the key bindings specification for further details.
199      *
200      * @deprecated As of Java 2 platform v1.3.
201      */
202     @Deprecated
203     protected KeyStroke endKey;
204     /**
205      * As of Java 2 platform v1.3 this previously undocumented field is no
206      * longer used.
207      * Key bindings are now defined by the LookAndFeel, please refer to
208      * the key bindings specification for further details.
209      *
210      * @deprecated As of Java 2 platform v1.3.
211      */
212     @Deprecated
213     protected KeyStroke dividerResizeToggleKey;
214 
215     /**
216      * As of Java 2 platform v1.3 this previously undocumented field is no
217      * longer used.
218      * Key bindings are now defined by the LookAndFeel, please refer to
219      * the key bindings specification for further details.
220      *
221      * @deprecated As of Java 2 platform v1.3.
222      */
223     @Deprecated
224     protected ActionListener keyboardUpLeftListener;
225     /**
226      * As of Java 2 platform v1.3 this previously undocumented field is no
227      * longer used.
228      * Key bindings are now defined by the LookAndFeel, please refer to
229      * the key bindings specification for further details.
230      *
231      * @deprecated As of Java 2 platform v1.3.
232      */
233     @Deprecated
234     protected ActionListener keyboardDownRightListener;
235     /**
236      * As of Java 2 platform v1.3 this previously undocumented field is no
237      * longer used.
238      * Key bindings are now defined by the LookAndFeel, please refer to
239      * the key bindings specification for further details.
240      *
241      * @deprecated As of Java 2 platform v1.3.
242      */
243     @Deprecated
244     protected ActionListener keyboardHomeListener;
245     /**
246      * As of Java 2 platform v1.3 this previously undocumented field is no
247      * longer used.
248      * Key bindings are now defined by the LookAndFeel, please refer to
249      * the key bindings specification for further details.
250      *
251      * @deprecated As of Java 2 platform v1.3.
252      */
253     @Deprecated
254     protected ActionListener keyboardEndListener;
255     /**
256      * As of Java 2 platform v1.3 this previously undocumented field is no
257      * longer used.
258      * Key bindings are now defined by the LookAndFeel, please refer to
259      * the key bindings specification for further details.
260      *
261      * @deprecated As of Java 2 platform v1.3.
262      */
263     @Deprecated
264     protected ActionListener keyboardResizeToggleListener;
265 
266 
267     // Private data of the instance
268     private int         orientation;
269     private int         lastDragLocation;
270     private boolean     continuousLayout;
271     private boolean     dividerKeyboardResize;
272     private boolean     dividerLocationIsSet;  // needed for tracking
273                                                // the first occurrence of
274                                                // setDividerLocation()
275     private Color dividerDraggingColor;
276     private boolean rememberPaneSizes;
277 
278     // Indicates whether the one of splitpane sides is expanded
279     private boolean keepHidden = false;
280 
281     /** Indicates that we have painted once. */
282     // This is used by the LayoutManager to determine when it should use
283     // the divider location provided by the JSplitPane. This is used as there
284     // is no way to determine when the layout process has completed.
285     boolean             painted;
286     /** If true, setDividerLocation does nothing. */
287     boolean             ignoreDividerLocationChange;
288 
289     /**
290      * Constructs a {@code BasicSplitPaneUI}.
291      */
BasicSplitPaneUI()292     public BasicSplitPaneUI() {}
293 
294     /**
295      * Creates a new instance of {@code BasicSplitPaneUI}.
296      *
297      * @param x a component
298      * @return a new instance of {@code BasicSplitPaneUI}
299      */
createUI(JComponent x)300     public static ComponentUI createUI(JComponent x) {
301         return new BasicSplitPaneUI();
302     }
303 
loadActionMap(LazyActionMap map)304     static void loadActionMap(LazyActionMap map) {
305         map.put(new Actions(Actions.NEGATIVE_INCREMENT));
306         map.put(new Actions(Actions.POSITIVE_INCREMENT));
307         map.put(new Actions(Actions.SELECT_MIN));
308         map.put(new Actions(Actions.SELECT_MAX));
309         map.put(new Actions(Actions.START_RESIZE));
310         map.put(new Actions(Actions.TOGGLE_FOCUS));
311         map.put(new Actions(Actions.FOCUS_OUT_FORWARD));
312         map.put(new Actions(Actions.FOCUS_OUT_BACKWARD));
313     }
314 
315 
316 
317     /**
318      * Installs the UI.
319      */
installUI(JComponent c)320     public void installUI(JComponent c) {
321         splitPane = (JSplitPane) c;
322         dividerLocationIsSet = false;
323         dividerKeyboardResize = false;
324         keepHidden = false;
325         installDefaults();
326         installListeners();
327         installKeyboardActions();
328         setLastDragLocation(-1);
329     }
330 
331 
332     /**
333      * Installs the UI defaults.
334      */
335     @SuppressWarnings("deprecation")
installDefaults()336     protected void installDefaults(){
337         LookAndFeel.installBorder(splitPane, "SplitPane.border");
338         LookAndFeel.installColors(splitPane, "SplitPane.background",
339                                   "SplitPane.foreground");
340         LookAndFeel.installProperty(splitPane, "opaque", Boolean.TRUE);
341 
342         if (divider == null) divider = createDefaultDivider();
343         divider.setBasicSplitPaneUI(this);
344 
345         Border    b = divider.getBorder();
346 
347         if (b == null || !(b instanceof UIResource)) {
348             divider.setBorder(UIManager.getBorder("SplitPaneDivider.border"));
349         }
350 
351         dividerDraggingColor = UIManager.getColor("SplitPaneDivider.draggingColor");
352 
353         setOrientation(splitPane.getOrientation());
354 
355         // note: don't rename this temp variable to dividerSize
356         // since it will conflict with "this.dividerSize" field
357         Integer temp = (Integer)UIManager.get("SplitPane.dividerSize");
358         LookAndFeel.installProperty(splitPane, "dividerSize", temp == null? 10: temp);
359 
360         divider.setDividerSize(splitPane.getDividerSize());
361         dividerSize = divider.getDividerSize();
362         splitPane.add(divider, JSplitPane.DIVIDER);
363 
364         setContinuousLayout(splitPane.isContinuousLayout());
365 
366         resetLayoutManager();
367 
368         /* Install the nonContinuousLayoutDivider here to avoid having to
369         add/remove everything later. */
370         if(nonContinuousLayoutDivider == null) {
371             setNonContinuousLayoutDivider(
372                                 createDefaultNonContinuousLayoutDivider(),
373                                 true);
374         } else {
375             setNonContinuousLayoutDivider(nonContinuousLayoutDivider, true);
376         }
377 
378         // focus forward traversal key
379         if (managingFocusForwardTraversalKeys==null) {
380             managingFocusForwardTraversalKeys = new HashSet<KeyStroke>();
381             managingFocusForwardTraversalKeys.add(
382                 KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
383         }
384         splitPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
385                                         managingFocusForwardTraversalKeys);
386         // focus backward traversal key
387         if (managingFocusBackwardTraversalKeys==null) {
388             managingFocusBackwardTraversalKeys = new HashSet<KeyStroke>();
389             managingFocusBackwardTraversalKeys.add(
390                 KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
391         }
392         splitPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
393                                         managingFocusBackwardTraversalKeys);
394     }
395 
396 
397     /**
398      * Installs the event listeners for the UI.
399      */
installListeners()400     protected void installListeners() {
401         if ((propertyChangeListener = createPropertyChangeListener()) !=
402             null) {
403             splitPane.addPropertyChangeListener(propertyChangeListener);
404         }
405 
406         if ((focusListener = createFocusListener()) != null) {
407             splitPane.addFocusListener(focusListener);
408         }
409     }
410 
411 
412     /**
413      * Installs the keyboard actions for the UI.
414      */
installKeyboardActions()415     protected void installKeyboardActions() {
416         InputMap km = getInputMap(JComponent.
417                                   WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
418 
419         SwingUtilities.replaceUIInputMap(splitPane, JComponent.
420                                        WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
421                                        km);
422         LazyActionMap.installLazyActionMap(splitPane, BasicSplitPaneUI.class,
423                                            "SplitPane.actionMap");
424     }
425 
getInputMap(int condition)426     InputMap getInputMap(int condition) {
427         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
428             return (InputMap)DefaultLookup.get(splitPane, this,
429                                                "SplitPane.ancestorInputMap");
430         }
431         return null;
432     }
433 
434     /**
435      * Uninstalls the UI.
436      */
uninstallUI(JComponent c)437     public void uninstallUI(JComponent c) {
438         uninstallKeyboardActions();
439         uninstallListeners();
440         uninstallDefaults();
441         dividerLocationIsSet = false;
442         dividerKeyboardResize = false;
443         splitPane = null;
444     }
445 
446 
447     /**
448      * Uninstalls the UI defaults.
449      */
uninstallDefaults()450     protected void uninstallDefaults() {
451         if(splitPane.getLayout() == layoutManager) {
452             splitPane.setLayout(null);
453         }
454 
455         if(nonContinuousLayoutDivider != null) {
456             splitPane.remove(nonContinuousLayoutDivider);
457         }
458 
459         LookAndFeel.uninstallBorder(splitPane);
460 
461         Border    b = divider.getBorder();
462 
463         if (b instanceof UIResource) {
464             divider.setBorder(null);
465         }
466 
467         splitPane.remove(divider);
468         divider.setBasicSplitPaneUI(null);
469         layoutManager = null;
470         divider = null;
471         nonContinuousLayoutDivider = null;
472 
473         setNonContinuousLayoutDivider(null);
474 
475         // sets the focus forward and backward traversal keys to null
476         // to restore the defaults
477         splitPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
478         splitPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);
479     }
480 
481 
482     /**
483      * Uninstalls the event listeners for the UI.
484      */
uninstallListeners()485     protected void uninstallListeners() {
486         if (propertyChangeListener != null) {
487             splitPane.removePropertyChangeListener(propertyChangeListener);
488             propertyChangeListener = null;
489         }
490         if (focusListener != null) {
491             splitPane.removeFocusListener(focusListener);
492             focusListener = null;
493         }
494 
495         keyboardUpLeftListener = null;
496         keyboardDownRightListener = null;
497         keyboardHomeListener = null;
498         keyboardEndListener = null;
499         keyboardResizeToggleListener = null;
500         handler = null;
501     }
502 
503 
504     /**
505      * Uninstalls the keyboard actions for the UI.
506      */
uninstallKeyboardActions()507     protected void uninstallKeyboardActions() {
508         SwingUtilities.replaceUIActionMap(splitPane, null);
509         SwingUtilities.replaceUIInputMap(splitPane, JComponent.
510                                       WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
511                                       null);
512     }
513 
514 
515     /**
516      * Creates a {@code PropertyChangeListener} for the {@code JSplitPane} UI.
517      *
518      * @return an instance of {@code PropertyChangeListener}
519      */
createPropertyChangeListener()520     protected PropertyChangeListener createPropertyChangeListener() {
521         return getHandler();
522     }
523 
getHandler()524     private Handler getHandler() {
525         if (handler == null) {
526             handler = new Handler();
527         }
528         return handler;
529     }
530 
531 
532     /**
533      * Creates a {@code FocusListener} for the {@code JSplitPane} UI.
534      *
535      * @return an instance of {@code FocusListener}
536      */
createFocusListener()537     protected FocusListener createFocusListener() {
538         return getHandler();
539     }
540 
541 
542     /**
543      * As of Java 2 platform v1.3 this method is no longer used.
544      * Subclassers previously using this method should instead create
545      * an {@code Action} wrapping the {@code ActionListener}, and register
546      * that {@code Action} by overriding {@code installKeyboardActions}
547      * and placing the {@code Action} in the {@code SplitPane's ActionMap}.
548      * Please refer to the key bindings specification for further details.
549      * <p>
550      * Creates an {@code ActionListener} for the {@code JSplitPane} UI that
551      * listens for specific key presses.
552      *
553      * @return an instance of {@code ActionListener}
554      * @deprecated As of Java 2 platform v1.3.
555      */
556     @Deprecated
createKeyboardUpLeftListener()557     protected ActionListener createKeyboardUpLeftListener() {
558         return new KeyboardUpLeftHandler();
559     }
560 
561 
562     /**
563      * As of Java 2 platform v1.3 this method is no longer used.
564      * Subclassers previously using this method should instead create
565      * an {@code Action} wrapping the {@code ActionListener}, and register
566      * that {@code Action} by overriding {@code installKeyboardActions}
567      * and placing the {@code Action} in the {@code SplitPane's ActionMap}.
568      * Please refer to the key bindings specification for further details.
569      * <p>
570      * Creates an {@code ActionListener} for the {@code JSplitPane} UI that
571      * listens for specific key presses.
572      *
573      * @return an instance of {@code ActionListener}
574      * @deprecated As of Java 2 platform v1.3.
575      */
576     @Deprecated
createKeyboardDownRightListener()577     protected ActionListener createKeyboardDownRightListener() {
578         return new KeyboardDownRightHandler();
579     }
580 
581 
582     /**
583      * As of Java 2 platform v1.3 this method is no longer used.
584      * Subclassers previously using this method should instead create
585      * an {@code Action} wrapping the {@code ActionListener}, and register
586      * that {@code Action} by overriding {@code installKeyboardActions}
587      * and placing the {@code Action} in the {@code SplitPane's ActionMap}.
588      * Please refer to the key bindings specification for further details.
589      * <p>
590      * Creates an {@code ActionListener} for the {@code JSplitPane} UI that
591      * listens for specific key presses.
592      *
593      * @return an instance of {@code ActionListener}
594      * @deprecated As of Java 2 platform v1.3.
595      */
596     @Deprecated
createKeyboardHomeListener()597     protected ActionListener createKeyboardHomeListener() {
598         return new KeyboardHomeHandler();
599     }
600 
601 
602     /**
603      * As of Java 2 platform v1.3 this method is no longer used.
604      * Subclassers previously using this method should instead create
605      * an {@code Action} wrapping the {@code ActionListener}, and register
606      * that {@code Action} by overriding {@code installKeyboardActions}
607      * and placing the {@code Action} in the {@code SplitPane's ActionMap}.
608      * Please refer to the key bindings specification for further details.
609      * <p>
610      * Creates an {@code ActionListener} for the {@code JSplitPane} UI that
611      * listens for specific key presses.
612      *
613      * @return an instance of {@code ActionListener}
614      * @deprecated As of Java 2 platform v1.3.
615      */
616     @Deprecated
createKeyboardEndListener()617     protected ActionListener createKeyboardEndListener() {
618         return new KeyboardEndHandler();
619     }
620 
621 
622     /**
623      * As of Java 2 platform v1.3 this method is no longer used.
624      * Subclassers previously using this method should instead create
625      * an {@code Action} wrapping the {@code ActionListener}, and register
626      * that {@code Action} by overriding {@code installKeyboardActions}
627      * and placing the {@code Action} in the {@code SplitPane's ActionMap}.
628      * Please refer to the key bindings specification for further details.
629      * <p>
630      * Creates an {@code ActionListener} for the {@code JSplitPane} UI that
631      * listens for specific key presses.
632      *
633      * @return an instance of {@code ActionListener}
634      * @deprecated As of Java 2 platform v1.3.
635      */
636     @Deprecated
createKeyboardResizeToggleListener()637     protected ActionListener createKeyboardResizeToggleListener() {
638         return new KeyboardResizeToggleHandler();
639     }
640 
641 
642     /**
643      * Returns the orientation for the {@code JSplitPane}.
644      *
645      * @return the orientation
646      */
getOrientation()647     public int getOrientation() {
648         return orientation;
649     }
650 
651 
652     /**
653      * Set the orientation for the {@code JSplitPane}.
654      *
655      * @param orientation the orientation
656      */
setOrientation(int orientation)657     public void setOrientation(int orientation) {
658         this.orientation = orientation;
659     }
660 
661 
662     /**
663      * Determines whether the {@code JSplitPane} is set to use a continuous layout.
664      *
665      * @return {@code true} if a continuous layout is set
666      */
isContinuousLayout()667     public boolean isContinuousLayout() {
668         return continuousLayout;
669     }
670 
671 
672     /**
673      * Turn continuous layout on/off.
674      *
675      * @param b if {@code true} the continuous layout turns on
676      */
setContinuousLayout(boolean b)677     public void setContinuousLayout(boolean b) {
678         continuousLayout = b;
679     }
680 
681 
682     /**
683      * Returns the last drag location of the {@code JSplitPane}.
684      *
685      * @return the last drag location
686      */
getLastDragLocation()687     public int getLastDragLocation() {
688         return lastDragLocation;
689     }
690 
691 
692     /**
693      * Set the last drag location of the {@code JSplitPane}.
694      *
695      * @param l the drag location
696      */
setLastDragLocation(int l)697     public void setLastDragLocation(int l) {
698         lastDragLocation = l;
699     }
700 
701     /**
702      * @return increment via keyboard methods.
703      */
getKeyboardMoveIncrement()704     int getKeyboardMoveIncrement() {
705         return 3;
706     }
707 
708     /**
709      * Implementation of the PropertyChangeListener
710      * that the JSplitPane UI uses.
711      * <p>
712      * This class should be treated as a &quot;protected&quot; inner class.
713      * Instantiate it only within subclasses of BasicSplitPaneUI.
714      */
715     public class PropertyHandler implements PropertyChangeListener
716     {
717         /**
718          * Constructs a {@code PropertyHandler}.
719          */
PropertyHandler()720         public PropertyHandler() {}
721 
722         // NOTE: This class exists only for backward compatibility. All
723         // its functionality has been moved into Handler. If you need to add
724         // new functionality add it to the Handler, but make sure this
725         // class calls into the Handler.
726 
727         /**
728          * Messaged from the <code>JSplitPane</code> the receiver is
729          * contained in.  May potentially reset the layout manager and cause a
730          * <code>validate</code> to be sent.
731          */
propertyChange(PropertyChangeEvent e)732         public void propertyChange(PropertyChangeEvent e) {
733             getHandler().propertyChange(e);
734         }
735     }
736 
737 
738     /**
739      * Implementation of the FocusListener that the JSplitPane UI uses.
740      * <p>
741      * This class should be treated as a &quot;protected&quot; inner class.
742      * Instantiate it only within subclasses of BasicSplitPaneUI.
743      */
744     public class FocusHandler extends FocusAdapter
745     {
746         /**
747          * Constructs a {@code FocusHandler}.
748          */
FocusHandler()749         public FocusHandler() {}
750 
751         // NOTE: This class exists only for backward compatibility. All
752         // its functionality has been moved into Handler. If you need to add
753         // new functionality add it to the Handler, but make sure this
754         // class calls into the Handler.
focusGained(FocusEvent ev)755         public void focusGained(FocusEvent ev) {
756             getHandler().focusGained(ev);
757         }
758 
focusLost(FocusEvent ev)759         public void focusLost(FocusEvent ev) {
760             getHandler().focusLost(ev);
761         }
762     }
763 
764 
765     /**
766      * Implementation of an ActionListener that the JSplitPane UI uses for
767      * handling specific key presses.
768      * <p>
769      * This class should be treated as a &quot;protected&quot; inner class.
770      * Instantiate it only within subclasses of BasicSplitPaneUI.
771      */
772     public class KeyboardUpLeftHandler implements ActionListener
773     {
774         /**
775          * Constructs a {@code KeyboardUpLeftHandler}.
776          */
KeyboardUpLeftHandler()777         public KeyboardUpLeftHandler() {}
778 
actionPerformed(ActionEvent ev)779         public void actionPerformed(ActionEvent ev) {
780             if (dividerKeyboardResize) {
781                 splitPane.setDividerLocation(Math.max(0,getDividerLocation
782                                   (splitPane) - getKeyboardMoveIncrement()));
783             }
784         }
785     }
786 
787     /**
788      * Implementation of an ActionListener that the JSplitPane UI uses for
789      * handling specific key presses.
790      * <p>
791      * This class should be treated as a &quot;protected&quot; inner class.
792      * Instantiate it only within subclasses of BasicSplitPaneUI.
793      */
794     public class KeyboardDownRightHandler implements ActionListener
795     {
796         /**
797          * Constructs a {@code KeyboardDownRightHandler}.
798          */
KeyboardDownRightHandler()799         public KeyboardDownRightHandler() {}
800 
actionPerformed(ActionEvent ev)801         public void actionPerformed(ActionEvent ev) {
802             if (dividerKeyboardResize) {
803                 splitPane.setDividerLocation(getDividerLocation(splitPane) +
804                                              getKeyboardMoveIncrement());
805             }
806         }
807     }
808 
809 
810     /**
811      * Implementation of an ActionListener that the JSplitPane UI uses for
812      * handling specific key presses.
813      * <p>
814      * This class should be treated as a &quot;protected&quot; inner class.
815      * Instantiate it only within subclasses of BasicSplitPaneUI.
816      */
817     public class KeyboardHomeHandler implements ActionListener
818     {
819         /**
820          * Constructs a {@code KeyboardHomeHandler}.
821          */
KeyboardHomeHandler()822         public KeyboardHomeHandler() {}
823 
actionPerformed(ActionEvent ev)824         public void actionPerformed(ActionEvent ev) {
825             if (dividerKeyboardResize) {
826                 splitPane.setDividerLocation(0);
827             }
828         }
829     }
830 
831 
832     /**
833      * Implementation of an ActionListener that the JSplitPane UI uses for
834      * handling specific key presses.
835      * <p>
836      * This class should be treated as a &quot;protected&quot; inner class.
837      * Instantiate it only within subclasses of BasicSplitPaneUI.
838      */
839     public class KeyboardEndHandler implements ActionListener
840     {
841         /**
842          * Constructs a {@code KeyboardEndHandler}.
843          */
KeyboardEndHandler()844         public KeyboardEndHandler() {}
845 
actionPerformed(ActionEvent ev)846         public void actionPerformed(ActionEvent ev) {
847             if (dividerKeyboardResize) {
848                 Insets   insets = splitPane.getInsets();
849                 int      bottomI = (insets != null) ? insets.bottom : 0;
850                 int      rightI = (insets != null) ? insets.right : 0;
851 
852                 if (orientation == JSplitPane.VERTICAL_SPLIT) {
853                     splitPane.setDividerLocation(splitPane.getHeight() -
854                                        bottomI);
855                 }
856                 else {
857                     splitPane.setDividerLocation(splitPane.getWidth() -
858                                                  rightI);
859                 }
860             }
861         }
862     }
863 
864 
865     /**
866      * Implementation of an ActionListener that the JSplitPane UI uses for
867      * handling specific key presses.
868      * <p>
869      * This class should be treated as a &quot;protected&quot; inner class.
870      * Instantiate it only within subclasses of BasicSplitPaneUI.
871      */
872     public class KeyboardResizeToggleHandler implements ActionListener
873     {
874         /**
875          * Constructs a {@code KeyboardResizeToggleHandler}.
876          */
KeyboardResizeToggleHandler()877         public KeyboardResizeToggleHandler() {}
878 
actionPerformed(ActionEvent ev)879         public void actionPerformed(ActionEvent ev) {
880             if (!dividerKeyboardResize) {
881                 splitPane.requestFocus();
882             }
883         }
884     }
885 
886     /**
887      * Returns the divider between the top Components.
888      *
889      * @return the divider between the top Components
890      */
getDivider()891     public BasicSplitPaneDivider getDivider() {
892         return divider;
893     }
894 
895 
896     /**
897      * Returns the default non continuous layout divider, which is an
898      * instance of {@code Canvas} that fills in the background with dark gray.
899      *
900      * @return the default non continuous layout divider
901      */
902     @SuppressWarnings("serial") // anonymous class
createDefaultNonContinuousLayoutDivider()903     protected Component createDefaultNonContinuousLayoutDivider() {
904         return new Canvas() {
905             public void paint(Graphics g) {
906                 if(!isContinuousLayout() && getLastDragLocation() != -1) {
907                     Dimension      size = splitPane.getSize();
908 
909                     g.setColor(dividerDraggingColor);
910                     if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
911                         g.fillRect(0, 0, dividerSize - 1, size.height - 1);
912                     } else {
913                         g.fillRect(0, 0, size.width - 1, dividerSize - 1);
914                     }
915                 }
916             }
917         };
918     }
919 
920 
921     /**
922      * Sets the divider to use when the {@code JSplitPane} is configured to
923      * not continuously layout. This divider will only be used during a
924      * dragging session. It is recommended that the passed in component
925      * be a heavy weight.
926      *
927      * @param newDivider the new divider
928      */
929     protected void setNonContinuousLayoutDivider(Component newDivider) {
930         setNonContinuousLayoutDivider(newDivider, true);
931     }
932 
933 
934     /**
935      * Sets the divider to use.
936      *
937      * @param newDivider the new divider
938      * @param rememberSizes if {@code true} the pane size is remembered
939      */
940     protected void setNonContinuousLayoutDivider(Component newDivider,
941         boolean rememberSizes) {
942         rememberPaneSizes = rememberSizes;
943         if(nonContinuousLayoutDivider != null && splitPane != null) {
944             splitPane.remove(nonContinuousLayoutDivider);
945         }
946         nonContinuousLayoutDivider = newDivider;
947     }
948 
949     private void addHeavyweightDivider() {
950         if(nonContinuousLayoutDivider != null && splitPane != null) {
951 
952             /* Needs to remove all the components and re-add them! YECK! */
953             // This is all done so that the nonContinuousLayoutDivider will
954             // be drawn on top of the other components, without this, one
955             // of the heavyweights will draw over the divider!
956             Component             leftC = splitPane.getLeftComponent();
957             Component             rightC = splitPane.getRightComponent();
958             int                   lastLocation = splitPane.
959                                               getDividerLocation();
960 
961             if(leftC != null)
962                 splitPane.setLeftComponent(null);
963             if(rightC != null)
964                 splitPane.setRightComponent(null);
965             splitPane.remove(divider);
966             splitPane.add(nonContinuousLayoutDivider, BasicSplitPaneUI.
967                           NON_CONTINUOUS_DIVIDER,
968                           splitPane.getComponentCount());
969             splitPane.setLeftComponent(leftC);
970             splitPane.setRightComponent(rightC);
971             splitPane.add(divider, JSplitPane.DIVIDER);
972             if(rememberPaneSizes) {
973                 splitPane.setDividerLocation(lastLocation);
974             }
975         }
976 
977     }
978 
979 
980     /**
981      * Returns the divider to use when the {@code JSplitPane} is configured to
982      * not continuously layout. This divider will only be used during a
983      * dragging session.
984      *
985      * @return the divider
986      */
987     public Component getNonContinuousLayoutDivider() {
988         return nonContinuousLayoutDivider;
989     }
990 
991 
992     /**
993      * Returns the {@code JSplitPane} this instance is currently contained
994      * in.
995      *
996      * @return the instance of {@code JSplitPane}
997      */
998     public JSplitPane getSplitPane() {
999         return splitPane;
1000     }
1001 
1002 
1003     /**
1004      * Creates the default divider.
1005      *
1006      * @return the default divider
1007      */
1008     public BasicSplitPaneDivider createDefaultDivider() {
1009         return new BasicSplitPaneDivider(this);
1010     }
1011 
1012 
1013     /**
1014      * Messaged to reset the preferred sizes.
1015      */
1016     public void resetToPreferredSizes(JSplitPane jc) {
1017         if(splitPane != null) {
1018             layoutManager.resetToPreferredSizes();
1019             splitPane.revalidate();
1020             splitPane.repaint();
1021         }
1022     }
1023 
1024 
1025     /**
1026      * Sets the location of the divider to location.
1027      */
1028     public void setDividerLocation(JSplitPane jc, int location) {
1029         if (!ignoreDividerLocationChange) {
1030             dividerLocationIsSet = true;
1031             splitPane.revalidate();
1032             splitPane.repaint();
1033 
1034             if (keepHidden) {
1035                 Insets insets = splitPane.getInsets();
1036                 int orientation = splitPane.getOrientation();
1037                 if ((orientation == JSplitPane.VERTICAL_SPLIT &&
1038                      location != insets.top &&
1039                      location != splitPane.getHeight()-divider.getHeight()-insets.top) ||
1040                     (orientation == JSplitPane.HORIZONTAL_SPLIT &&
1041                      location != insets.left &&
1042                      location != splitPane.getWidth()-divider.getWidth()-insets.left)) {
1043                     setKeepHidden(false);
1044                 }
1045             }
1046         }
1047         else {
1048             ignoreDividerLocationChange = false;
1049         }
1050     }
1051 
1052 
1053     /**
1054      * Returns the location of the divider, which may differ from what
1055      * the splitpane thinks the location of the divider is.
1056      */
1057     public int getDividerLocation(JSplitPane jc) {
1058         if(orientation == JSplitPane.HORIZONTAL_SPLIT)
1059             return divider.getLocation().x;
1060         return divider.getLocation().y;
1061     }
1062 
1063 
1064     /**
1065      * Gets the minimum location of the divider.
1066      */
1067     public int getMinimumDividerLocation(JSplitPane jc) {
1068         int       minLoc = 0;
1069         Component leftC = splitPane.getLeftComponent();
1070 
1071         if ((leftC != null) && (leftC.isVisible())) {
1072             Insets    insets = splitPane.getInsets();
1073             Dimension minSize = leftC.getMinimumSize();
1074             if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1075                 minLoc = minSize.width;
1076             } else {
1077                 minLoc = minSize.height;
1078             }
1079             if(insets != null) {
1080                 if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1081                     minLoc += insets.left;
1082                 } else {
1083                     minLoc += insets.top;
1084                 }
1085             }
1086         }
1087         return minLoc;
1088     }
1089 
1090 
1091     /**
1092      * Gets the maximum location of the divider.
1093      */
1094     public int getMaximumDividerLocation(JSplitPane jc) {
1095         Dimension splitPaneSize = splitPane.getSize();
1096         int       maxLoc = 0;
1097         Component rightC = splitPane.getRightComponent();
1098 
1099         if (rightC != null) {
1100             Insets    insets = splitPane.getInsets();
1101             Dimension minSize = new Dimension(0, 0);
1102             if (rightC.isVisible()) {
1103                 minSize = rightC.getMinimumSize();
1104             }
1105             if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1106                 maxLoc = splitPaneSize.width - minSize.width;
1107             } else {
1108                 maxLoc = splitPaneSize.height - minSize.height;
1109             }
1110             maxLoc -= dividerSize;
1111             if(insets != null) {
1112                 if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1113                     maxLoc -= insets.right;
1114                 } else {
1115                     maxLoc -= insets.top;
1116                 }
1117             }
1118         }
1119         return Math.max(getMinimumDividerLocation(splitPane), maxLoc);
1120     }
1121 
1122 
1123     /**
1124      * Called when the specified split pane has finished painting
1125      * its children.
1126      */
1127     public void finishedPaintingChildren(JSplitPane sp, Graphics g) {
1128         if(sp == splitPane && getLastDragLocation() != -1 &&
1129            !isContinuousLayout() && !draggingHW) {
1130             Dimension      size = splitPane.getSize();
1131 
1132             g.setColor(dividerDraggingColor);
1133             if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1134                 g.fillRect(getLastDragLocation(), 0, dividerSize - 1,
1135                            size.height - 1);
1136             } else {
1137                 g.fillRect(0, lastDragLocation, size.width - 1,
1138                            dividerSize - 1);
1139             }
1140         }
1141     }
1142 
1143 
1144     /**
1145      * {@inheritDoc}
1146      */
1147     public void paint(Graphics g, JComponent jc) {
1148         if (!painted && splitPane.getDividerLocation()<0) {
1149             ignoreDividerLocationChange = true;
1150             splitPane.setDividerLocation(getDividerLocation(splitPane));
1151         }
1152         painted = true;
1153     }
1154 
1155 
1156     /**
1157      * Returns the preferred size for the passed in component,
1158      * This is passed off to the current layout manager.
1159      */
1160     public Dimension getPreferredSize(JComponent jc) {
1161         if(splitPane != null)
1162             return layoutManager.preferredLayoutSize(splitPane);
1163         return new Dimension(0, 0);
1164     }
1165 
1166 
1167     /**
1168      * Returns the minimum size for the passed in component,
1169      * This is passed off to the current layout manager.
1170      */
1171     public Dimension getMinimumSize(JComponent jc) {
1172         if(splitPane != null)
1173             return layoutManager.minimumLayoutSize(splitPane);
1174         return new Dimension(0, 0);
1175     }
1176 
1177 
1178     /**
1179      * Returns the maximum size for the passed in component,
1180      * This is passed off to the current layout manager.
1181      */
1182     public Dimension getMaximumSize(JComponent jc) {
1183         if(splitPane != null)
1184             return layoutManager.maximumLayoutSize(splitPane);
1185         return new Dimension(0, 0);
1186     }
1187 
1188 
1189     /**
1190      * Returns the insets. The insets are returned from the border insets
1191      * of the current border.
1192      *
1193      * @param jc a component
1194      * @return the insets
1195      */
1196     public Insets getInsets(JComponent jc) {
1197         return null;
1198     }
1199 
1200 
1201     /**
1202      * Resets the layout manager based on orientation and messages it
1203      * with invalidateLayout to pull in appropriate Components.
1204      */
1205     protected void resetLayoutManager() {
1206         if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1207             layoutManager = new BasicHorizontalLayoutManager(0);
1208         } else {
1209             layoutManager = new BasicHorizontalLayoutManager(1);
1210         }
1211         splitPane.setLayout(layoutManager);
1212         layoutManager.updateComponents();
1213         splitPane.revalidate();
1214         splitPane.repaint();
1215     }
1216 
1217     /**
1218      * Set the value to indicate if one of the splitpane sides is expanded.
1219      */
1220     void setKeepHidden(boolean keepHidden) {
1221         this.keepHidden = keepHidden;
1222     }
1223 
1224     /**
1225      * The value returned indicates if one of the splitpane sides is expanded.
1226      * @return true if one of the splitpane sides is expanded, false otherwise.
1227      */
1228     private boolean getKeepHidden() {
1229         return keepHidden;
1230     }
1231 
1232     /**
1233      * Should be messaged before the dragging session starts, resets
1234      * lastDragLocation and dividerSize.
1235      */
1236     protected void startDragging() {
1237         Component       leftC = splitPane.getLeftComponent();
1238         Component       rightC = splitPane.getRightComponent();
1239         ComponentPeer   cPeer;
1240 
1241         beginDragDividerLocation = getDividerLocation(splitPane);
1242         draggingHW = false;
1243         final ComponentAccessor acc = AWTAccessor.getComponentAccessor();
1244         if(leftC != null && (cPeer = acc.getPeer(leftC)) != null &&
1245            !(cPeer instanceof LightweightPeer)) {
1246             draggingHW = true;
1247         } else if(rightC != null && (cPeer = acc.getPeer(rightC)) != null
1248                   && !(cPeer instanceof LightweightPeer)) {
1249             draggingHW = true;
1250         }
1251         if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1252             setLastDragLocation(divider.getBounds().x);
1253             dividerSize = divider.getSize().width;
1254             if(!isContinuousLayout() && draggingHW) {
1255                 nonContinuousLayoutDivider.setBounds
1256                         (getLastDragLocation(), 0, dividerSize,
1257                          splitPane.getHeight());
1258                       addHeavyweightDivider();
1259             }
1260         } else {
1261             setLastDragLocation(divider.getBounds().y);
1262             dividerSize = divider.getSize().height;
1263             if(!isContinuousLayout() && draggingHW) {
1264                 nonContinuousLayoutDivider.setBounds
1265                         (0, getLastDragLocation(), splitPane.getWidth(),
1266                          dividerSize);
1267                       addHeavyweightDivider();
1268             }
1269         }
1270     }
1271 
1272 
1273     /**
1274      * Messaged during a dragging session to move the divider to the
1275      * passed in {@code location}. If {@code continuousLayout} is {@code true}
1276      * the location is reset and the splitPane validated.
1277      *
1278      * @param location the location of divider
1279      */
1280     protected void dragDividerTo(int location) {
1281         if(getLastDragLocation() != location) {
1282             if(isContinuousLayout()) {
1283                 splitPane.setDividerLocation(location);
1284                 setLastDragLocation(location);
1285             } else {
1286                 int lastLoc = getLastDragLocation();
1287 
1288                 setLastDragLocation(location);
1289                 if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1290                     if(draggingHW) {
1291                         nonContinuousLayoutDivider.setLocation(
1292                             getLastDragLocation(), 0);
1293                     } else {
1294                         int   splitHeight = splitPane.getHeight();
1295                         splitPane.repaint(lastLoc, 0, dividerSize,
1296                                           splitHeight);
1297                         splitPane.repaint(location, 0, dividerSize,
1298                                           splitHeight);
1299                     }
1300                 } else {
1301                     if(draggingHW) {
1302                         nonContinuousLayoutDivider.setLocation(0,
1303                             getLastDragLocation());
1304                     } else {
1305                         int    splitWidth = splitPane.getWidth();
1306 
1307                         splitPane.repaint(0, lastLoc, splitWidth,
1308                                           dividerSize);
1309                         splitPane.repaint(0, location, splitWidth,
1310                                           dividerSize);
1311                     }
1312                 }
1313             }
1314         }
1315     }
1316 
1317 
1318     /**
1319      * Messaged to finish the dragging session. If not continuous display
1320      * the dividers {@code location} will be reset.
1321      *
1322      * @param location the location of divider
1323      */
1324     protected void finishDraggingTo(int location) {
1325         dragDividerTo(location);
1326         setLastDragLocation(-1);
1327         if(!isContinuousLayout()) {
1328             Component   leftC = splitPane.getLeftComponent();
1329             Rectangle   leftBounds = leftC.getBounds();
1330 
1331             if (draggingHW) {
1332                 if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1333                     nonContinuousLayoutDivider.setLocation(-dividerSize, 0);
1334                 }
1335                 else {
1336                     nonContinuousLayoutDivider.setLocation(0, -dividerSize);
1337                 }
1338                 splitPane.remove(nonContinuousLayoutDivider);
1339             }
1340             splitPane.setDividerLocation(location);
1341         }
1342     }
1343 
1344 
1345     /**
1346      * As of Java 2 platform v1.3 this method is no longer used. Instead
1347      * you should set the border on the divider.
1348      * <p>
1349      * Returns the width of one side of the divider border.
1350      *
1351      * @return the width of one side of the divider border
1352      * @deprecated As of Java 2 platform v1.3, instead set the border on the
1353      * divider.
1354      */
1355     @Deprecated
1356     protected int getDividerBorderSize() {
1357         return 1;
1358     }
1359 
1360 
1361     /**
1362      * LayoutManager for JSplitPanes that have an orientation of
1363      * HORIZONTAL_SPLIT.
1364      */
1365     public class BasicHorizontalLayoutManager implements LayoutManager2
1366     {
1367         /* left, right, divider. (in this exact order) */
1368         /**
1369          * The size of components.
1370          */
1371         protected int[]         sizes;
1372         /**
1373          * The components.
1374          */
1375         protected Component[]   components;
1376         /** Size of the splitpane the last time laid out. */
1377         private int             lastSplitPaneSize;
1378         /** True if resetToPreferredSizes has been invoked. */
1379         private boolean         doReset;
1380         /** Axis, 0 for horizontal, or 1 for veritcal. */
1381         private int             axis;
1382 
1383 
1384         BasicHorizontalLayoutManager() {
1385             this(0);
1386         }
1387 
1388         BasicHorizontalLayoutManager(int axis) {
1389             this.axis = axis;
1390             components = new Component[3];
1391             components[0] = components[1] = components[2] = null;
1392             sizes = new int[3];
1393         }
1394 
1395         //
1396         // LayoutManager
1397         //
1398 
1399         /**
1400          * Does the actual layout.
1401          */
1402         public void layoutContainer(Container container) {
1403             Dimension   containerSize = container.getSize();
1404 
1405             // If the splitpane has a zero size then no op out of here.
1406             // If we execute this function now, we're going to cause ourselves
1407             // much grief.
1408             if (containerSize.height <= 0 || containerSize.width <= 0 ) {
1409                 lastSplitPaneSize = 0;
1410                 return;
1411             }
1412 
1413             int         spDividerLocation = splitPane.getDividerLocation();
1414             Insets      insets = splitPane.getInsets();
1415             int         availableSize = getAvailableSize(containerSize,
1416                                                          insets);
1417             int         newSize = getSizeForPrimaryAxis(containerSize);
1418             int         beginLocation = getDividerLocation(splitPane);
1419             int         dOffset = getSizeForPrimaryAxis(insets, true);
1420             Dimension   dSize = (components[2] == null) ? null :
1421                                  components[2].getPreferredSize();
1422 
1423             if ((doReset && !dividerLocationIsSet) || spDividerLocation < 0) {
1424                 resetToPreferredSizes(availableSize);
1425             }
1426             else if (lastSplitPaneSize <= 0 ||
1427                      availableSize == lastSplitPaneSize || !painted ||
1428                      (dSize != null &&
1429                       getSizeForPrimaryAxis(dSize) != sizes[2])) {
1430                 if (dSize != null) {
1431                     sizes[2] = getSizeForPrimaryAxis(dSize);
1432                 }
1433                 else {
1434                     sizes[2] = 0;
1435                 }
1436                 setDividerLocation(spDividerLocation - dOffset, availableSize);
1437                 dividerLocationIsSet = false;
1438             }
1439             else if (availableSize != lastSplitPaneSize) {
1440                 distributeSpace(availableSize - lastSplitPaneSize,
1441                                 getKeepHidden());
1442             }
1443             doReset = false;
1444             dividerLocationIsSet = false;
1445             lastSplitPaneSize = availableSize;
1446 
1447             // Reset the bounds of each component
1448             int nextLocation = getInitialLocation(insets);
1449             int counter = 0;
1450 
1451             while (counter < 3) {
1452                 if (components[counter] != null &&
1453                     components[counter].isVisible()) {
1454                     setComponentToSize(components[counter], sizes[counter],
1455                                        nextLocation, insets, containerSize);
1456                     nextLocation += sizes[counter];
1457                 }
1458                 switch (counter) {
1459                 case 0:
1460                     counter = 2;
1461                     break;
1462                 case 2:
1463                     counter = 1;
1464                     break;
1465                 case 1:
1466                     counter = 3;
1467                     break;
1468                 }
1469             }
1470             if (painted) {
1471                 // This is tricky, there is never a good time for us
1472                 // to push the value to the splitpane, painted appears to
1473                 // the best time to do it. What is really needed is
1474                 // notification that layout has completed.
1475                 int      newLocation = getDividerLocation(splitPane);
1476 
1477                 if (newLocation != (spDividerLocation - dOffset)) {
1478                     int  lastLocation = splitPane.getLastDividerLocation();
1479 
1480                     ignoreDividerLocationChange = true;
1481                     try {
1482                         splitPane.setDividerLocation(newLocation);
1483                         // This is not always needed, but is rather tricky
1484                         // to determine when... The case this is needed for
1485                         // is if the user sets the divider location to some
1486                         // bogus value, say 0, and the actual value is 1, the
1487                         // call to setDividerLocation(1) will preserve the
1488                         // old value of 0, when we really want the divider
1489                         // location value  before the call. This is needed for
1490                         // the one touch buttons.
1491                         splitPane.setLastDividerLocation(lastLocation);
1492                     } finally {
1493                         ignoreDividerLocationChange = false;
1494                     }
1495                 }
1496             }
1497         }
1498 
1499 
1500         /**
1501          * Adds the component at place.  Place must be one of
1502          * JSplitPane.LEFT, RIGHT, TOP, BOTTOM, or null (for the
1503          * divider).
1504          */
1505         public void addLayoutComponent(String place, Component component) {
1506             boolean isValid = true;
1507 
1508             if(place != null) {
1509                 if(place.equals(JSplitPane.DIVIDER)) {
1510                     /* Divider. */
1511                     components[2] = component;
1512                     sizes[2] = getSizeForPrimaryAxis(component.
1513                                                      getPreferredSize());
1514                 } else if(place.equals(JSplitPane.LEFT) ||
1515                           place.equals(JSplitPane.TOP)) {
1516                     components[0] = component;
1517                     sizes[0] = 0;
1518                 } else if(place.equals(JSplitPane.RIGHT) ||
1519                           place.equals(JSplitPane.BOTTOM)) {
1520                     components[1] = component;
1521                     sizes[1] = 0;
1522                 } else if(!place.equals(
1523                                     BasicSplitPaneUI.NON_CONTINUOUS_DIVIDER))
1524                     isValid = false;
1525             } else {
1526                 isValid = false;
1527             }
1528             if(!isValid)
1529                 throw new IllegalArgumentException("cannot add to layout: " +
1530                     "unknown constraint: " +
1531                     place);
1532             doReset = true;
1533         }
1534 
1535 
1536         /**
1537          * Returns the minimum size needed to contain the children.
1538          * The width is the sum of all the children's min widths and
1539          * the height is the largest of the children's minimum heights.
1540          */
1541         public Dimension minimumLayoutSize(Container container) {
1542             int         minPrimary = 0;
1543             int         minSecondary = 0;
1544             Insets      insets = splitPane.getInsets();
1545 
1546             for (int counter=0; counter<3; counter++) {
1547                 if(components[counter] != null) {
1548                     Dimension   minSize = components[counter].getMinimumSize();
1549                     int         secSize = getSizeForSecondaryAxis(minSize);
1550 
1551                     minPrimary += getSizeForPrimaryAxis(minSize);
1552                     if(secSize > minSecondary)
1553                         minSecondary = secSize;
1554                 }
1555             }
1556             if(insets != null) {
1557                 minPrimary += getSizeForPrimaryAxis(insets, true) +
1558                               getSizeForPrimaryAxis(insets, false);
1559                 minSecondary += getSizeForSecondaryAxis(insets, true) +
1560                               getSizeForSecondaryAxis(insets, false);
1561             }
1562             if (axis == 0) {
1563                 return new Dimension(minPrimary, minSecondary);
1564             }
1565             return new Dimension(minSecondary, minPrimary);
1566         }
1567 
1568 
1569         /**
1570          * Returns the preferred size needed to contain the children.
1571          * The width is the sum of all the preferred widths of the children and
1572          * the height is the largest preferred height of the children.
1573          */
1574         public Dimension preferredLayoutSize(Container container) {
1575             int         prePrimary = 0;
1576             int         preSecondary = 0;
1577             Insets      insets = splitPane.getInsets();
1578 
1579             for(int counter = 0; counter < 3; counter++) {
1580                 if(components[counter] != null) {
1581                     Dimension   preSize = components[counter].
1582                                           getPreferredSize();
1583                     int         secSize = getSizeForSecondaryAxis(preSize);
1584 
1585                     prePrimary += getSizeForPrimaryAxis(preSize);
1586                     if(secSize > preSecondary)
1587                         preSecondary = secSize;
1588                 }
1589             }
1590             if(insets != null) {
1591                 prePrimary += getSizeForPrimaryAxis(insets, true) +
1592                               getSizeForPrimaryAxis(insets, false);
1593                 preSecondary += getSizeForSecondaryAxis(insets, true) +
1594                               getSizeForSecondaryAxis(insets, false);
1595             }
1596             if (axis == 0) {
1597                 return new Dimension(prePrimary, preSecondary);
1598             }
1599             return new Dimension(preSecondary, prePrimary);
1600         }
1601 
1602 
1603         /**
1604          * Removes the specified component from our knowledge.
1605          */
1606         public void removeLayoutComponent(Component component) {
1607             for(int counter = 0; counter < 3; counter++) {
1608                 if(components[counter] == component) {
1609                     components[counter] = null;
1610                     sizes[counter] = 0;
1611                     doReset = true;
1612                 }
1613             }
1614         }
1615 
1616 
1617         //
1618         // LayoutManager2
1619         //
1620 
1621 
1622         /**
1623          * Adds the specified component to the layout, using the specified
1624          * constraint object.
1625          * @param comp the component to be added
1626          * @param constraints  where/how the component is added to the layout.
1627          */
1628         public void addLayoutComponent(Component comp, Object constraints) {
1629             if ((constraints == null) || (constraints instanceof String)) {
1630                 addLayoutComponent((String)constraints, comp);
1631             } else {
1632                 throw new IllegalArgumentException("cannot add to layout: " +
1633                                                    "constraint must be a " +
1634                                                    "string (or null)");
1635             }
1636         }
1637 
1638 
1639         /**
1640          * Returns the alignment along the x axis.  This specifies how
1641          * the component would like to be aligned relative to other
1642          * components.  The value should be a number between 0 and 1
1643          * where 0 represents alignment along the origin, 1 is aligned
1644          * the furthest away from the origin, 0.5 is centered, etc.
1645          */
1646         public float getLayoutAlignmentX(Container target) {
1647             return 0.0f;
1648         }
1649 
1650 
1651         /**
1652          * Returns the alignment along the y axis.  This specifies how
1653          * the component would like to be aligned relative to other
1654          * components.  The value should be a number between 0 and 1
1655          * where 0 represents alignment along the origin, 1 is aligned
1656          * the furthest away from the origin, 0.5 is centered, etc.
1657          */
1658         public float getLayoutAlignmentY(Container target) {
1659             return 0.0f;
1660         }
1661 
1662 
1663         /**
1664          * Does nothing. If the developer really wants to change the
1665          * size of one of the views JSplitPane.resetToPreferredSizes should
1666          * be messaged.
1667          */
1668         public void invalidateLayout(Container c) {
1669         }
1670 
1671 
1672         /**
1673          * Returns the maximum layout size, which is Integer.MAX_VALUE
1674          * in both directions.
1675          */
1676         public Dimension maximumLayoutSize(Container target) {
1677             return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1678         }
1679 
1680 
1681         //
1682         // New methods.
1683         //
1684 
1685         /**
1686          * Marks the receiver so that the next time this instance is
1687          * laid out it'll ask for the preferred sizes.
1688          */
1689         public void resetToPreferredSizes() {
1690             doReset = true;
1691         }
1692 
1693         /**
1694          * Resets the size of the Component at the passed in location.
1695          *
1696          * @param index the index of a component
1697          */
1698         protected void resetSizeAt(int index) {
1699             sizes[index] = 0;
1700             doReset = true;
1701         }
1702 
1703 
1704         /**
1705          * Sets the sizes to {@code newSizes}.
1706          *
1707          * @param newSizes the new sizes
1708          */
1709         protected void setSizes(int[] newSizes) {
1710             System.arraycopy(newSizes, 0, sizes, 0, 3);
1711         }
1712 
1713 
1714         /**
1715          * Returns the sizes of the components.
1716          *
1717          * @return the sizes of the components
1718          */
1719         protected int[] getSizes() {
1720             int[]         retSizes = new int[3];
1721 
1722             System.arraycopy(sizes, 0, retSizes, 0, 3);
1723             return retSizes;
1724         }
1725 
1726 
1727         /**
1728          * Returns the width of the passed in Components preferred size.
1729          *
1730          * @param c a component
1731          * @return the preferred width of the component
1732          */
1733         protected int getPreferredSizeOfComponent(Component c) {
1734             return getSizeForPrimaryAxis(c.getPreferredSize());
1735         }
1736 
1737 
1738         /**
1739          * Returns the width of the passed in Components minimum size.
1740          *
1741          * @param c a component
1742          * @return the minimum width of the component
1743          */
1744         int getMinimumSizeOfComponent(Component c) {
1745             return getSizeForPrimaryAxis(c.getMinimumSize());
1746         }
1747 
1748 
1749         /**
1750          * Returns the width of the passed in component.
1751          *
1752          * @param c a component
1753          * @return the width of the component
1754          */
1755         protected int getSizeOfComponent(Component c) {
1756             return getSizeForPrimaryAxis(c.getSize());
1757         }
1758 
1759 
1760         /**
1761          * Returns the available width based on the container size and
1762          * {@code Insets}.
1763          *
1764          * @param containerSize a container size
1765          * @param insets an insets
1766          * @return the available width
1767          */
1768         protected int getAvailableSize(Dimension containerSize,
1769                                        Insets insets) {
1770             if(insets == null)
1771                 return getSizeForPrimaryAxis(containerSize);
1772             return (getSizeForPrimaryAxis(containerSize) -
1773                     (getSizeForPrimaryAxis(insets, true) +
1774                      getSizeForPrimaryAxis(insets, false)));
1775         }
1776 
1777 
1778         /**
1779          * Returns the left inset, unless the {@code Insets} are null in which case
1780          * 0 is returned.
1781          *
1782          * @param insets the insets
1783          * @return the left inset
1784          */
1785         protected int getInitialLocation(Insets insets) {
1786             if(insets != null)
1787                 return getSizeForPrimaryAxis(insets, true);
1788             return 0;
1789         }
1790 
1791 
1792         /**
1793          * Sets the width of the component {@code c} to be {@code size}, placing its
1794          * x location at {@code location}, y to the {@code insets.top} and height
1795          * to the {@code containerSize.height} less the top and bottom insets.
1796          *
1797          * @param c a component
1798          * @param size a new width
1799          * @param location a new X coordinate
1800          * @param insets an insets
1801          * @param containerSize a container size
1802          */
1803         protected void setComponentToSize(Component c, int size,
1804                                           int location, Insets insets,
1805                                           Dimension containerSize) {
1806             if(insets != null) {
1807                 if (axis == 0) {
1808                     c.setBounds(location, insets.top, size,
1809                                 containerSize.height -
1810                                 (insets.top + insets.bottom));
1811                 }
1812                 else {
1813                     c.setBounds(insets.left, location, containerSize.width -
1814                                 (insets.left + insets.right), size);
1815                 }
1816             }
1817             else {
1818                 if (axis == 0) {
1819                     c.setBounds(location, 0, size, containerSize.height);
1820                 }
1821                 else {
1822                     c.setBounds(0, location, containerSize.width, size);
1823                 }
1824             }
1825         }
1826 
1827         /**
1828          * If the axis == 0, the width is returned, otherwise the height.
1829          */
1830         int getSizeForPrimaryAxis(Dimension size) {
1831             if (axis == 0) {
1832                 return size.width;
1833             }
1834             return size.height;
1835         }
1836 
1837         /**
1838          * If the axis == 0, the width is returned, otherwise the height.
1839          */
1840         int getSizeForSecondaryAxis(Dimension size) {
1841             if (axis == 0) {
1842                 return size.height;
1843             }
1844             return size.width;
1845         }
1846 
1847         /**
1848          * Returns a particular value of the inset identified by the
1849          * axis and <code>isTop</code><p>
1850          *   axis isTop
1851          *    0    true    - left
1852          *    0    false   - right
1853          *    1    true    - top
1854          *    1    false   - bottom
1855          */
1856         int getSizeForPrimaryAxis(Insets insets, boolean isTop) {
1857             if (axis == 0) {
1858                 if (isTop) {
1859                     return insets.left;
1860                 }
1861                 return insets.right;
1862             }
1863             if (isTop) {
1864                 return insets.top;
1865             }
1866             return insets.bottom;
1867         }
1868 
1869         /**
1870          * Returns a particular value of the inset identified by the
1871          * axis and <code>isTop</code><p>
1872          *   axis isTop
1873          *    0    true    - left
1874          *    0    false   - right
1875          *    1    true    - top
1876          *    1    false   - bottom
1877          */
1878         int getSizeForSecondaryAxis(Insets insets, boolean isTop) {
1879             if (axis == 0) {
1880                 if (isTop) {
1881                     return insets.top;
1882                 }
1883                 return insets.bottom;
1884             }
1885             if (isTop) {
1886                 return insets.left;
1887             }
1888             return insets.right;
1889         }
1890 
1891         /**
1892          * Determines the components. This should be called whenever
1893          * a new instance of this is installed into an existing
1894          * SplitPane.
1895          */
1896         protected void updateComponents() {
1897             Component comp;
1898 
1899             comp = splitPane.getLeftComponent();
1900             if(components[0] != comp) {
1901                 components[0] = comp;
1902                 if(comp == null) {
1903                     sizes[0] = 0;
1904                 } else {
1905                     sizes[0] = -1;
1906                 }
1907             }
1908 
1909             comp = splitPane.getRightComponent();
1910             if(components[1] != comp) {
1911                 components[1] = comp;
1912                 if(comp == null) {
1913                     sizes[1] = 0;
1914                 } else {
1915                     sizes[1] = -1;
1916                 }
1917             }
1918 
1919             /* Find the divider. */
1920             Component[] children = splitPane.getComponents();
1921             Component   oldDivider = components[2];
1922 
1923             components[2] = null;
1924             for(int counter = children.length - 1; counter >= 0; counter--) {
1925                 if(children[counter] != components[0] &&
1926                    children[counter] != components[1] &&
1927                    children[counter] != nonContinuousLayoutDivider) {
1928                     if(oldDivider != children[counter]) {
1929                         components[2] = children[counter];
1930                     } else {
1931                         components[2] = oldDivider;
1932                     }
1933                     break;
1934                 }
1935             }
1936             if(components[2] == null) {
1937                 sizes[2] = 0;
1938             }
1939             else {
1940                 sizes[2] = getSizeForPrimaryAxis(components[2].getPreferredSize());
1941             }
1942         }
1943 
1944         /**
1945          * Resets the size of the first component to <code>leftSize</code>,
1946          * and the right component to the remainder of the space.
1947          */
1948         void setDividerLocation(int leftSize, int availableSize) {
1949             boolean          lValid = (components[0] != null &&
1950                                        components[0].isVisible());
1951             boolean          rValid = (components[1] != null &&
1952                                        components[1].isVisible());
1953             boolean          dValid = (components[2] != null &&
1954                                        components[2].isVisible());
1955             int              max = availableSize;
1956 
1957             if (dValid) {
1958                 max -= sizes[2];
1959             }
1960             leftSize = Math.max(0, Math.min(leftSize, max));
1961             if (lValid) {
1962                 if (rValid) {
1963                     sizes[0] = leftSize;
1964                     sizes[1] = max - leftSize;
1965                 }
1966                 else {
1967                     sizes[0] = max;
1968                     sizes[1] = 0;
1969                 }
1970             }
1971             else if (rValid) {
1972                 sizes[1] = max;
1973                 sizes[0] = 0;
1974             }
1975         }
1976 
1977         /**
1978          * Returns an array of the minimum sizes of the components.
1979          */
1980         int[] getPreferredSizes() {
1981             int[]         retValue = new int[3];
1982 
1983             for (int counter = 0; counter < 3; counter++) {
1984                 if (components[counter] != null &&
1985                     components[counter].isVisible()) {
1986                     retValue[counter] = getPreferredSizeOfComponent
1987                                         (components[counter]);
1988                 }
1989                 else {
1990                     retValue[counter] = -1;
1991                 }
1992             }
1993             return retValue;
1994         }
1995 
1996         /**
1997          * Returns an array of the minimum sizes of the components.
1998          */
1999         int[] getMinimumSizes() {
2000             int[]         retValue = new int[3];
2001 
2002             for (int counter = 0; counter < 2; counter++) {
2003                 if (components[counter] != null &&
2004                     components[counter].isVisible()) {
2005                     retValue[counter] = getMinimumSizeOfComponent
2006                                         (components[counter]);
2007                 }
2008                 else {
2009                     retValue[counter] = -1;
2010                 }
2011             }
2012             retValue[2] = (components[2] != null) ?
2013                 getMinimumSizeOfComponent(components[2]) : -1;
2014             return retValue;
2015         }
2016 
2017         /**
2018          * Resets the components to their preferred sizes.
2019          */
2020         void resetToPreferredSizes(int availableSize) {
2021             // Set the sizes to the preferred sizes (if fits), otherwise
2022             // set to min sizes and distribute any extra space.
2023             int[]       testSizes = getPreferredSizes();
2024             int         totalSize = 0;
2025 
2026             for (int counter = 0; counter < 3; counter++) {
2027                 if (testSizes[counter] != -1) {
2028                     totalSize += testSizes[counter];
2029                 }
2030             }
2031             if (totalSize > availableSize) {
2032                 testSizes = getMinimumSizes();
2033 
2034                 totalSize = 0;
2035                 for (int counter = 0; counter < 3; counter++) {
2036                     if (testSizes[counter] != -1) {
2037                         totalSize += testSizes[counter];
2038                     }
2039                 }
2040             }
2041             setSizes(testSizes);
2042             distributeSpace(availableSize - totalSize, false);
2043         }
2044 
2045         /**
2046          * Distributes <code>space</code> between the two components
2047          * (divider won't get any extra space) based on the weighting. This
2048          * attempts to honor the min size of the components.
2049          *
2050          * @param keepHidden if true and one of the components is 0x0
2051          *                   it gets none of the extra space
2052          */
2053         void distributeSpace(int space, boolean keepHidden) {
2054             boolean          lValid = (components[0] != null &&
2055                                        components[0].isVisible());
2056             boolean          rValid = (components[1] != null &&
2057                                        components[1].isVisible());
2058 
2059             if (keepHidden) {
2060                 if (lValid && getSizeForPrimaryAxis(
2061                                  components[0].getSize()) == 0) {
2062                     lValid = false;
2063                     if (rValid && getSizeForPrimaryAxis(
2064                                      components[1].getSize()) == 0) {
2065                         // Both aren't valid, force them both to be valid
2066                         lValid = true;
2067                     }
2068                 }
2069                 else if (rValid && getSizeForPrimaryAxis(
2070                                    components[1].getSize()) == 0) {
2071                     rValid = false;
2072                 }
2073             }
2074             if (lValid && rValid) {
2075                 double        weight = splitPane.getResizeWeight();
2076                 int           lExtra = (int)(weight * (double)space);
2077                 int           rExtra = (space - lExtra);
2078 
2079                 sizes[0] += lExtra;
2080                 sizes[1] += rExtra;
2081 
2082                 int           lMin = getMinimumSizeOfComponent(components[0]);
2083                 int           rMin = getMinimumSizeOfComponent(components[1]);
2084                 boolean       lMinValid = (sizes[0] >= lMin);
2085                 boolean       rMinValid = (sizes[1] >= rMin);
2086 
2087                 if (!lMinValid && !rMinValid) {
2088                     if (sizes[0] < 0) {
2089                         sizes[1] += sizes[0];
2090                         sizes[0] = 0;
2091                     }
2092                     else if (sizes[1] < 0) {
2093                         sizes[0] += sizes[1];
2094                         sizes[1] = 0;
2095                     }
2096                 }
2097                 else if (!lMinValid) {
2098                     if (sizes[1] - (lMin - sizes[0]) < rMin) {
2099                         // both below min, just make sure > 0
2100                         if (sizes[0] < 0) {
2101                             sizes[1] += sizes[0];
2102                             sizes[0] = 0;
2103                         }
2104                     }
2105                     else {
2106                         sizes[1] -= (lMin - sizes[0]);
2107                         sizes[0] = lMin;
2108                     }
2109                 }
2110                 else if (!rMinValid) {
2111                     if (sizes[0] - (rMin - sizes[1]) < lMin) {
2112                         // both below min, just make sure > 0
2113                         if (sizes[1] < 0) {
2114                             sizes[0] += sizes[1];
2115                             sizes[1] = 0;
2116                         }
2117                     }
2118                     else {
2119                         sizes[0] -= (rMin - sizes[1]);
2120                         sizes[1] = rMin;
2121                     }
2122                 }
2123                 if (sizes[0] < 0) {
2124                     sizes[0] = 0;
2125                 }
2126                 if (sizes[1] < 0) {
2127                     sizes[1] = 0;
2128                 }
2129             }
2130             else if (lValid) {
2131                 sizes[0] = Math.max(0, sizes[0] + space);
2132             }
2133             else if (rValid) {
2134                 sizes[1] = Math.max(0, sizes[1] + space);
2135             }
2136         }
2137     }
2138 
2139 
2140     /**
2141      * LayoutManager used for JSplitPanes with an orientation of
2142      * VERTICAL_SPLIT.
2143      *
2144      */
2145     public class BasicVerticalLayoutManager extends
2146             BasicHorizontalLayoutManager
2147     {
2148         /**
2149          * Constructs a new instance of {@code BasicVerticalLayoutManager}.
2150          */
2151         public BasicVerticalLayoutManager() {
2152             super(1);
2153         }
2154     }
2155 
2156 
2157     private class Handler implements FocusListener, PropertyChangeListener {
2158         //
2159         // PropertyChangeListener
2160         //
2161         /**
2162          * Messaged from the <code>JSplitPane</code> the receiver is
2163          * contained in.  May potentially reset the layout manager and cause a
2164          * <code>validate</code> to be sent.
2165          */
2166         public void propertyChange(PropertyChangeEvent e) {
2167             if(e.getSource() == splitPane) {
2168                 String changeName = e.getPropertyName();
2169 
2170                 if(changeName == JSplitPane.ORIENTATION_PROPERTY) {
2171                     orientation = splitPane.getOrientation();
2172                     resetLayoutManager();
2173                 } else if(changeName == JSplitPane.CONTINUOUS_LAYOUT_PROPERTY){
2174                     setContinuousLayout(splitPane.isContinuousLayout());
2175                     if(!isContinuousLayout()) {
2176                         if(nonContinuousLayoutDivider == null) {
2177                             setNonContinuousLayoutDivider(
2178                                 createDefaultNonContinuousLayoutDivider(),
2179                                 true);
2180                         } else if(nonContinuousLayoutDivider.getParent() ==
2181                                   null) {
2182                             setNonContinuousLayoutDivider(
2183                                 nonContinuousLayoutDivider,
2184                                 true);
2185                         }
2186                     }
2187                 } else if(changeName == JSplitPane.DIVIDER_SIZE_PROPERTY){
2188                     divider.setDividerSize(splitPane.getDividerSize());
2189                     dividerSize = divider.getDividerSize();
2190                     splitPane.revalidate();
2191                     splitPane.repaint();
2192                 }
2193             }
2194         }
2195 
2196         //
2197         // FocusListener
2198         //
2199         public void focusGained(FocusEvent ev) {
2200             dividerKeyboardResize = true;
2201             splitPane.repaint();
2202         }
2203 
2204         public void focusLost(FocusEvent ev) {
2205             dividerKeyboardResize = false;
2206             splitPane.repaint();
2207         }
2208     }
2209 
2210 
2211     private static class Actions extends UIAction {
2212         private static final String NEGATIVE_INCREMENT = "negativeIncrement";
2213         private static final String POSITIVE_INCREMENT = "positiveIncrement";
2214         private static final String SELECT_MIN = "selectMin";
2215         private static final String SELECT_MAX = "selectMax";
2216         private static final String START_RESIZE = "startResize";
2217         private static final String TOGGLE_FOCUS = "toggleFocus";
2218         private static final String FOCUS_OUT_FORWARD = "focusOutForward";
2219         private static final String FOCUS_OUT_BACKWARD = "focusOutBackward";
2220 
2221         Actions(String key) {
2222             super(key);
2223         }
2224 
2225         public void actionPerformed(ActionEvent ev) {
2226             JSplitPane splitPane = (JSplitPane)ev.getSource();
2227             BasicSplitPaneUI ui = (BasicSplitPaneUI)BasicLookAndFeel.
2228                       getUIOfType(splitPane.getUI(), BasicSplitPaneUI.class);
2229 
2230             if (ui == null) {
2231                 return;
2232             }
2233             String key = getName();
2234             if (key == NEGATIVE_INCREMENT) {
2235                 if (ui.dividerKeyboardResize) {
2236                     splitPane.setDividerLocation(Math.max(
2237                               0, ui.getDividerLocation
2238                               (splitPane) - ui.getKeyboardMoveIncrement()));
2239                 }
2240             }
2241             else if (key == POSITIVE_INCREMENT) {
2242                 if (ui.dividerKeyboardResize) {
2243                     splitPane.setDividerLocation(
2244                         ui.getDividerLocation(splitPane) +
2245                         ui.getKeyboardMoveIncrement());
2246                 }
2247             }
2248             else if (key == SELECT_MIN) {
2249                 if (ui.dividerKeyboardResize) {
2250                     splitPane.setDividerLocation(0);
2251                 }
2252             }
2253             else if (key == SELECT_MAX) {
2254                 if (ui.dividerKeyboardResize) {
2255                     Insets   insets = splitPane.getInsets();
2256                     int      bottomI = (insets != null) ? insets.bottom : 0;
2257                     int      rightI = (insets != null) ? insets.right : 0;
2258 
2259                     if (ui.orientation == JSplitPane.VERTICAL_SPLIT) {
2260                         splitPane.setDividerLocation(splitPane.getHeight() -
2261                                                      bottomI);
2262                     }
2263                     else {
2264                         splitPane.setDividerLocation(splitPane.getWidth() -
2265                                                      rightI);
2266                     }
2267                 }
2268             }
2269             else if (key == START_RESIZE) {
2270                 if (!ui.dividerKeyboardResize) {
2271                     splitPane.requestFocus();
2272                 } else {
2273                     JSplitPane parentSplitPane =
2274                         (JSplitPane)SwingUtilities.getAncestorOfClass(
2275                                          JSplitPane.class, splitPane);
2276                     if (parentSplitPane!=null) {
2277                         parentSplitPane.requestFocus();
2278                     }
2279                 }
2280             }
2281             else if (key == TOGGLE_FOCUS) {
2282                 toggleFocus(splitPane);
2283             }
2284             else if (key == FOCUS_OUT_FORWARD) {
2285                 moveFocus(splitPane, 1);
2286             }
2287             else if (key == FOCUS_OUT_BACKWARD) {
2288                 moveFocus(splitPane, -1);
2289             }
2290         }
2291 
2292         private void moveFocus(JSplitPane splitPane, int direction) {
2293             Container rootAncestor = splitPane.getFocusCycleRootAncestor();
2294             FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy();
2295             Component focusOn = (direction > 0) ?
2296                 policy.getComponentAfter(rootAncestor, splitPane) :
2297                 policy.getComponentBefore(rootAncestor, splitPane);
2298             HashSet<Component> focusFrom = new HashSet<Component>();
2299             if (splitPane.isAncestorOf(focusOn)) {
2300                 do {
2301                     focusFrom.add(focusOn);
2302                     rootAncestor = focusOn.getFocusCycleRootAncestor();
2303                     policy = rootAncestor.getFocusTraversalPolicy();
2304                     focusOn = (direction > 0) ?
2305                         policy.getComponentAfter(rootAncestor, focusOn) :
2306                         policy.getComponentBefore(rootAncestor, focusOn);
2307                 } while (splitPane.isAncestorOf(focusOn) &&
2308                          !focusFrom.contains(focusOn));
2309             }
2310             if ( focusOn!=null && !splitPane.isAncestorOf(focusOn) ) {
2311                 focusOn.requestFocus();
2312             }
2313         }
2314 
2315         private void toggleFocus(JSplitPane splitPane) {
2316             Component left = splitPane.getLeftComponent();
2317             Component right = splitPane.getRightComponent();
2318 
2319             KeyboardFocusManager manager =
2320                 KeyboardFocusManager.getCurrentKeyboardFocusManager();
2321             Component focus = manager.getFocusOwner();
2322             Component focusOn = getNextSide(splitPane, focus);
2323             if (focusOn != null) {
2324                 // don't change the focus if the new focused component belongs
2325                 // to the same splitpane and the same side
2326                 if ( focus!=null &&
2327                      ( (SwingUtilities.isDescendingFrom(focus, left) &&
2328                         SwingUtilities.isDescendingFrom(focusOn, left)) ||
2329                        (SwingUtilities.isDescendingFrom(focus, right) &&
2330                         SwingUtilities.isDescendingFrom(focusOn, right)) ) ) {
2331                     return;
2332                 }
2333                 SwingUtilities2.compositeRequestFocus(focusOn);
2334             }
2335         }
2336 
2337         private Component getNextSide(JSplitPane splitPane, Component focus) {
2338             Component left = splitPane.getLeftComponent();
2339             Component right = splitPane.getRightComponent();
2340             Component next;
2341             if (focus!=null && SwingUtilities.isDescendingFrom(focus, left) &&
2342                 right!=null) {
2343                 next = getFirstAvailableComponent(right);
2344                 if (next != null) {
2345                     return next;
2346                 }
2347             }
2348             JSplitPane parentSplitPane = (JSplitPane)SwingUtilities.getAncestorOfClass(JSplitPane.class, splitPane);
2349             if (parentSplitPane!=null) {
2350                 // focus next side of the parent split pane
2351                 next = getNextSide(parentSplitPane, focus);
2352             } else {
2353                 next = getFirstAvailableComponent(left);
2354                 if (next == null) {
2355                     next = getFirstAvailableComponent(right);
2356                 }
2357             }
2358             return next;
2359         }
2360 
2361         private Component getFirstAvailableComponent(Component c) {
2362             if (c!=null && c instanceof JSplitPane) {
2363                 JSplitPane sp = (JSplitPane)c;
2364                 Component left = getFirstAvailableComponent(sp.getLeftComponent());
2365                 if (left != null) {
2366                     c = left;
2367                 } else {
2368                     c = getFirstAvailableComponent(sp.getRightComponent());
2369                 }
2370             }
2371             return c;
2372         }
2373     }
2374 }
2375