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