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