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