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     protected void startDragging() {
1195         Component       leftC = splitPane.getLeftComponent();
1196         Component       rightC = splitPane.getRightComponent();
1197         ComponentPeer   cPeer;
1198 
1199         beginDragDividerLocation = getDividerLocation(splitPane);
1200         draggingHW = false;
1201         if(leftC != null && (cPeer = leftC.getPeer()) != null &&
1202            !(cPeer instanceof LightweightPeer)) {
1203             draggingHW = true;
1204         } else if(rightC != null && (cPeer = rightC.getPeer()) != null
1205                   && !(cPeer instanceof LightweightPeer)) {
1206             draggingHW = true;
1207         }
1208         if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1209             setLastDragLocation(divider.getBounds().x);
1210             dividerSize = divider.getSize().width;
1211             if(!isContinuousLayout() && draggingHW) {
1212                 nonContinuousLayoutDivider.setBounds
1213                         (getLastDragLocation(), 0, dividerSize,
1214                          splitPane.getHeight());
1215                       addHeavyweightDivider();
1216             }
1217         } else {
1218             setLastDragLocation(divider.getBounds().y);
1219             dividerSize = divider.getSize().height;
1220             if(!isContinuousLayout() && draggingHW) {
1221                 nonContinuousLayoutDivider.setBounds
1222                         (0, getLastDragLocation(), splitPane.getWidth(),
1223                          dividerSize);
1224                       addHeavyweightDivider();
1225             }
1226         }
1227     }
1228 
1229 
1230     /**
1231      * Messaged during a dragging session to move the divider to the
1232      * passed in {@code location}. If {@code continuousLayout} is {@code true}
1233      * the location is reset and the splitPane validated.
1234      *
1235      * @param location the location of divider
1236      */
1237     protected void dragDividerTo(int location) {
1238         if(getLastDragLocation() != location) {
1239             if(isContinuousLayout()) {
1240                 splitPane.setDividerLocation(location);
1241                 setLastDragLocation(location);
1242             } else {
1243                 int lastLoc = getLastDragLocation();
1244 
1245                 setLastDragLocation(location);
1246                 if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1247                     if(draggingHW) {
1248                         nonContinuousLayoutDivider.setLocation(
1249                             getLastDragLocation(), 0);
1250                     } else {
1251                         int   splitHeight = splitPane.getHeight();
1252                         splitPane.repaint(lastLoc, 0, dividerSize,
1253                                           splitHeight);
1254                         splitPane.repaint(location, 0, dividerSize,
1255                                           splitHeight);
1256                     }
1257                 } else {
1258                     if(draggingHW) {
1259                         nonContinuousLayoutDivider.setLocation(0,
1260                             getLastDragLocation());
1261                     } else {
1262                         int    splitWidth = splitPane.getWidth();
1263 
1264                         splitPane.repaint(0, lastLoc, splitWidth,
1265                                           dividerSize);
1266                         splitPane.repaint(0, location, splitWidth,
1267                                           dividerSize);
1268                     }
1269                 }
1270             }
1271         }
1272     }
1273 
1274 
1275     /**
1276      * Messaged to finish the dragging session. If not continuous display
1277      * the dividers {@code location} will be reset.
1278      *
1279      * @param location the location of divider
1280      */
1281     protected void finishDraggingTo(int location) {
1282         dragDividerTo(location);
1283         setLastDragLocation(-1);
1284         if(!isContinuousLayout()) {
1285             Component   leftC = splitPane.getLeftComponent();
1286             Rectangle   leftBounds = leftC.getBounds();
1287 
1288             if (draggingHW) {
1289                 if(orientation == JSplitPane.HORIZONTAL_SPLIT) {
1290                     nonContinuousLayoutDivider.setLocation(-dividerSize, 0);
1291                 }
1292                 else {
1293                     nonContinuousLayoutDivider.setLocation(0, -dividerSize);
1294                 }
1295                 splitPane.remove(nonContinuousLayoutDivider);
1296             }
1297             splitPane.setDividerLocation(location);
1298         }
1299     }
1300 
1301 
1302     /**
1303      * As of Java 2 platform v1.3 this method is no longer used. Instead
1304      * you should set the border on the divider.
1305      * <p>
1306      * Returns the width of one side of the divider border.
1307      *
1308      * @return the width of one side of the divider border
1309      * @deprecated As of Java 2 platform v1.3, instead set the border on the
1310      * divider.
1311      */
1312     @Deprecated
1313     protected int getDividerBorderSize() {
1314         return 1;
1315     }
1316 
1317 
1318     /**
1319      * LayoutManager for JSplitPanes that have an orientation of
1320      * HORIZONTAL_SPLIT.
1321      */
1322     public class BasicHorizontalLayoutManager implements LayoutManager2
1323     {
1324         /* left, right, divider. (in this exact order) */
1325         /**
1326          * The size of components.
1327          */
1328         protected int[]         sizes;
1329         /**
1330          * The components.
1331          */
1332         protected Component[]   components;
1333         /** Size of the splitpane the last time laid out. */
1334         private int             lastSplitPaneSize;
1335         /** True if resetToPreferredSizes has been invoked. */
1336         private boolean         doReset;
1337         /** Axis, 0 for horizontal, or 1 for veritcal. */
1338         private int             axis;
1339 
1340 
1341         BasicHorizontalLayoutManager() {
1342             this(0);
1343         }
1344 
1345         BasicHorizontalLayoutManager(int axis) {
1346             this.axis = axis;
1347             components = new Component[3];
1348             components[0] = components[1] = components[2] = null;
1349             sizes = new int[3];
1350         }
1351 
1352         //
1353         // LayoutManager
1354         //
1355 
1356         /**
1357          * Does the actual layout.
1358          */
1359         public void layoutContainer(Container container) {
1360             Dimension   containerSize = container.getSize();
1361 
1362             // If the splitpane has a zero size then no op out of here.
1363             // If we execute this function now, we're going to cause ourselves
1364             // much grief.
1365             if (containerSize.height <= 0 || containerSize.width <= 0 ) {
1366                 lastSplitPaneSize = 0;
1367                 return;
1368             }
1369 
1370             int         spDividerLocation = splitPane.getDividerLocation();
1371             Insets      insets = splitPane.getInsets();
1372             int         availableSize = getAvailableSize(containerSize,
1373                                                          insets);
1374             int         newSize = getSizeForPrimaryAxis(containerSize);
1375             int         beginLocation = getDividerLocation(splitPane);
1376             int         dOffset = getSizeForPrimaryAxis(insets, true);
1377             Dimension   dSize = (components[2] == null) ? null :
1378                                  components[2].getPreferredSize();
1379 
1380             if ((doReset && !dividerLocationIsSet) || spDividerLocation < 0) {
1381                 resetToPreferredSizes(availableSize);
1382             }
1383             else if (lastSplitPaneSize <= 0 ||
1384                      availableSize == lastSplitPaneSize || !painted ||
1385                      (dSize != null &&
1386                       getSizeForPrimaryAxis(dSize) != sizes[2])) {
1387                 if (dSize != null) {
1388                     sizes[2] = getSizeForPrimaryAxis(dSize);
1389                 }
1390                 else {
1391                     sizes[2] = 0;
1392                 }
1393                 setDividerLocation(spDividerLocation - dOffset, availableSize);
1394                 dividerLocationIsSet = false;
1395             }
1396             else if (availableSize != lastSplitPaneSize) {
1397                 distributeSpace(availableSize - lastSplitPaneSize,
1398                                 getKeepHidden());
1399             }
1400             doReset = false;
1401             dividerLocationIsSet = false;
1402             lastSplitPaneSize = availableSize;
1403 
1404             // Reset the bounds of each component
1405             int nextLocation = getInitialLocation(insets);
1406             int counter = 0;
1407 
1408             while (counter < 3) {
1409                 if (components[counter] != null &&
1410                     components[counter].isVisible()) {
1411                     setComponentToSize(components[counter], sizes[counter],
1412                                        nextLocation, insets, containerSize);
1413                     nextLocation += sizes[counter];
1414                 }
1415                 switch (counter) {
1416                 case 0:
1417                     counter = 2;
1418                     break;
1419                 case 2:
1420                     counter = 1;
1421                     break;
1422                 case 1:
1423                     counter = 3;
1424                     break;
1425                 }
1426             }
1427             if (painted) {
1428                 // This is tricky, there is never a good time for us
1429                 // to push the value to the splitpane, painted appears to
1430                 // the best time to do it. What is really needed is
1431                 // notification that layout has completed.
1432                 int      newLocation = getDividerLocation(splitPane);
1433 
1434                 if (newLocation != (spDividerLocation - dOffset)) {
1435                     int  lastLocation = splitPane.getLastDividerLocation();
1436 
1437                     ignoreDividerLocationChange = true;
1438                     try {
1439                         splitPane.setDividerLocation(newLocation);
1440                         // This is not always needed, but is rather tricky
1441                         // to determine when... The case this is needed for
1442                         // is if the user sets the divider location to some
1443                         // bogus value, say 0, and the actual value is 1, the
1444                         // call to setDividerLocation(1) will preserve the
1445                         // old value of 0, when we really want the divider
1446                         // location value  before the call. This is needed for
1447                         // the one touch buttons.
1448                         splitPane.setLastDividerLocation(lastLocation);
1449                     } finally {
1450                         ignoreDividerLocationChange = false;
1451                     }
1452                 }
1453             }
1454         }
1455 
1456 
1457         /**
1458          * Adds the component at place.  Place must be one of
1459          * JSplitPane.LEFT, RIGHT, TOP, BOTTOM, or null (for the
1460          * divider).
1461          */
1462         public void addLayoutComponent(String place, Component component) {
1463             boolean isValid = true;
1464 
1465             if(place != null) {
1466                 if(place.equals(JSplitPane.DIVIDER)) {
1467                     /* Divider. */
1468                     components[2] = component;
1469                     sizes[2] = getSizeForPrimaryAxis(component.
1470                                                      getPreferredSize());
1471                 } else if(place.equals(JSplitPane.LEFT) ||
1472                           place.equals(JSplitPane.TOP)) {
1473                     components[0] = component;
1474                     sizes[0] = 0;
1475                 } else if(place.equals(JSplitPane.RIGHT) ||
1476                           place.equals(JSplitPane.BOTTOM)) {
1477                     components[1] = component;
1478                     sizes[1] = 0;
1479                 } else if(!place.equals(
1480                                     BasicSplitPaneUI.NON_CONTINUOUS_DIVIDER))
1481                     isValid = false;
1482             } else {
1483                 isValid = false;
1484             }
1485             if(!isValid)
1486                 throw new IllegalArgumentException("cannot add to layout: " +
1487                     "unknown constraint: " +
1488                     place);
1489             doReset = true;
1490         }
1491 
1492 
1493         /**
1494          * Returns the minimum size needed to contain the children.
1495          * The width is the sum of all the children's min widths and
1496          * the height is the largest of the children's minimum heights.
1497          */
1498         public Dimension minimumLayoutSize(Container container) {
1499             int         minPrimary = 0;
1500             int         minSecondary = 0;
1501             Insets      insets = splitPane.getInsets();
1502 
1503             for (int counter=0; counter<3; counter++) {
1504                 if(components[counter] != null) {
1505                     Dimension   minSize = components[counter].getMinimumSize();
1506                     int         secSize = getSizeForSecondaryAxis(minSize);
1507 
1508                     minPrimary += getSizeForPrimaryAxis(minSize);
1509                     if(secSize > minSecondary)
1510                         minSecondary = secSize;
1511                 }
1512             }
1513             if(insets != null) {
1514                 minPrimary += getSizeForPrimaryAxis(insets, true) +
1515                               getSizeForPrimaryAxis(insets, false);
1516                 minSecondary += getSizeForSecondaryAxis(insets, true) +
1517                               getSizeForSecondaryAxis(insets, false);
1518             }
1519             if (axis == 0) {
1520                 return new Dimension(minPrimary, minSecondary);
1521             }
1522             return new Dimension(minSecondary, minPrimary);
1523         }
1524 
1525 
1526         /**
1527          * Returns the preferred size needed to contain the children.
1528          * The width is the sum of all the preferred widths of the children and
1529          * the height is the largest preferred height of the children.
1530          */
1531         public Dimension preferredLayoutSize(Container container) {
1532             int         prePrimary = 0;
1533             int         preSecondary = 0;
1534             Insets      insets = splitPane.getInsets();
1535 
1536             for(int counter = 0; counter < 3; counter++) {
1537                 if(components[counter] != null) {
1538                     Dimension   preSize = components[counter].
1539                                           getPreferredSize();
1540                     int         secSize = getSizeForSecondaryAxis(preSize);
1541 
1542                     prePrimary += getSizeForPrimaryAxis(preSize);
1543                     if(secSize > preSecondary)
1544                         preSecondary = secSize;
1545                 }
1546             }
1547             if(insets != null) {
1548                 prePrimary += getSizeForPrimaryAxis(insets, true) +
1549                               getSizeForPrimaryAxis(insets, false);
1550                 preSecondary += getSizeForSecondaryAxis(insets, true) +
1551                               getSizeForSecondaryAxis(insets, false);
1552             }
1553             if (axis == 0) {
1554                 return new Dimension(prePrimary, preSecondary);
1555             }
1556             return new Dimension(preSecondary, prePrimary);
1557         }
1558 
1559 
1560         /**
1561          * Removes the specified component from our knowledge.
1562          */
1563         public void removeLayoutComponent(Component component) {
1564             for(int counter = 0; counter < 3; counter++) {
1565                 if(components[counter] == component) {
1566                     components[counter] = null;
1567                     sizes[counter] = 0;
1568                     doReset = true;
1569                 }
1570             }
1571         }
1572 
1573 
1574         //
1575         // LayoutManager2
1576         //
1577 
1578 
1579         /**
1580          * Adds the specified component to the layout, using the specified
1581          * constraint object.
1582          * @param comp the component to be added
1583          * @param constraints  where/how the component is added to the layout.
1584          */
1585         public void addLayoutComponent(Component comp, Object constraints) {
1586             if ((constraints == null) || (constraints instanceof String)) {
1587                 addLayoutComponent((String)constraints, comp);
1588             } else {
1589                 throw new IllegalArgumentException("cannot add to layout: " +
1590                                                    "constraint must be a " +
1591                                                    "string (or null)");
1592             }
1593         }
1594 
1595 
1596         /**
1597          * Returns the alignment along the x axis.  This specifies how
1598          * the component would like to be aligned relative to other
1599          * components.  The value should be a number between 0 and 1
1600          * where 0 represents alignment along the origin, 1 is aligned
1601          * the furthest away from the origin, 0.5 is centered, etc.
1602          */
1603         public float getLayoutAlignmentX(Container target) {
1604             return 0.0f;
1605         }
1606 
1607 
1608         /**
1609          * Returns the alignment along the y axis.  This specifies how
1610          * the component would like to be aligned relative to other
1611          * components.  The value should be a number between 0 and 1
1612          * where 0 represents alignment along the origin, 1 is aligned
1613          * the furthest away from the origin, 0.5 is centered, etc.
1614          */
1615         public float getLayoutAlignmentY(Container target) {
1616             return 0.0f;
1617         }
1618 
1619 
1620         /**
1621          * Does nothing. If the developer really wants to change the
1622          * size of one of the views JSplitPane.resetToPreferredSizes should
1623          * be messaged.
1624          */
1625         public void invalidateLayout(Container c) {
1626         }
1627 
1628 
1629         /**
1630          * Returns the maximum layout size, which is Integer.MAX_VALUE
1631          * in both directions.
1632          */
1633         public Dimension maximumLayoutSize(Container target) {
1634             return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1635         }
1636 
1637 
1638         //
1639         // New methods.
1640         //
1641 
1642         /**
1643          * Marks the receiver so that the next time this instance is
1644          * laid out it'll ask for the preferred sizes.
1645          */
1646         public void resetToPreferredSizes() {
1647             doReset = true;
1648         }
1649 
1650         /**
1651          * Resets the size of the Component at the passed in location.
1652          *
1653          * @param index the index of a component
1654          */
1655         protected void resetSizeAt(int index) {
1656             sizes[index] = 0;
1657             doReset = true;
1658         }
1659 
1660 
1661         /**
1662          * Sets the sizes to {@code newSizes}.
1663          *
1664          * @param newSizes the new sizes
1665          */
1666         protected void setSizes(int[] newSizes) {
1667             System.arraycopy(newSizes, 0, sizes, 0, 3);
1668         }
1669 
1670 
1671         /**
1672          * Returns the sizes of the components.
1673          *
1674          * @return the sizes of the components
1675          */
1676         protected int[] getSizes() {
1677             int[]         retSizes = new int[3];
1678 
1679             System.arraycopy(sizes, 0, retSizes, 0, 3);
1680             return retSizes;
1681         }
1682 
1683 
1684         /**
1685          * Returns the width of the passed in Components preferred size.
1686          *
1687          * @param c a component
1688          * @return the preferred width of the component
1689          */
1690         protected int getPreferredSizeOfComponent(Component c) {
1691             return getSizeForPrimaryAxis(c.getPreferredSize());
1692         }
1693 
1694 
1695         /**
1696          * Returns the width of the passed in Components minimum size.
1697          *
1698          * @param c a component
1699          * @return the minimum width of the component
1700          */
1701         int getMinimumSizeOfComponent(Component c) {
1702             return getSizeForPrimaryAxis(c.getMinimumSize());
1703         }
1704 
1705 
1706         /**
1707          * Returns the width of the passed in component.
1708          *
1709          * @param c a component
1710          * @return the width of the component
1711          */
1712         protected int getSizeOfComponent(Component c) {
1713             return getSizeForPrimaryAxis(c.getSize());
1714         }
1715 
1716 
1717         /**
1718          * Returns the available width based on the container size and
1719          * {@code Insets}.
1720          *
1721          * @param containerSize a container size
1722          * @param insets an insets
1723          * @return the available width
1724          */
1725         protected int getAvailableSize(Dimension containerSize,
1726                                        Insets insets) {
1727             if(insets == null)
1728                 return getSizeForPrimaryAxis(containerSize);
1729             return (getSizeForPrimaryAxis(containerSize) -
1730                     (getSizeForPrimaryAxis(insets, true) +
1731                      getSizeForPrimaryAxis(insets, false)));
1732         }
1733 
1734 
1735         /**
1736          * Returns the left inset, unless the {@code Insets} are null in which case
1737          * 0 is returned.
1738          *
1739          * @param insets the insets
1740          * @return the left inset
1741          */
1742         protected int getInitialLocation(Insets insets) {
1743             if(insets != null)
1744                 return getSizeForPrimaryAxis(insets, true);
1745             return 0;
1746         }
1747 
1748 
1749         /**
1750          * Sets the width of the component {@code c} to be {@code size}, placing its
1751          * x location at {@code location}, y to the {@code insets.top} and height
1752          * to the {@code containerSize.height} less the top and bottom insets.
1753          *
1754          * @param c a component
1755          * @param size a new width
1756          * @param location a new X coordinate
1757          * @param insets an insets
1758          * @param containerSize a container size
1759          */
1760         protected void setComponentToSize(Component c, int size,
1761                                           int location, Insets insets,
1762                                           Dimension containerSize) {
1763             if(insets != null) {
1764                 if (axis == 0) {
1765                     c.setBounds(location, insets.top, size,
1766                                 containerSize.height -
1767                                 (insets.top + insets.bottom));
1768                 }
1769                 else {
1770                     c.setBounds(insets.left, location, containerSize.width -
1771                                 (insets.left + insets.right), size);
1772                 }
1773             }
1774             else {
1775                 if (axis == 0) {
1776                     c.setBounds(location, 0, size, containerSize.height);
1777                 }
1778                 else {
1779                     c.setBounds(0, location, containerSize.width, size);
1780                 }
1781             }
1782         }
1783 
1784         /**
1785          * If the axis == 0, the width is returned, otherwise the height.
1786          */
1787         int getSizeForPrimaryAxis(Dimension size) {
1788             if (axis == 0) {
1789                 return size.width;
1790             }
1791             return size.height;
1792         }
1793 
1794         /**
1795          * If the axis == 0, the width is returned, otherwise the height.
1796          */
1797         int getSizeForSecondaryAxis(Dimension size) {
1798             if (axis == 0) {
1799                 return size.height;
1800             }
1801             return size.width;
1802         }
1803 
1804         /**
1805          * Returns a particular value of the inset identified by the
1806          * axis and <code>isTop</code><p>
1807          *   axis isTop
1808          *    0    true    - left
1809          *    0    false   - right
1810          *    1    true    - top
1811          *    1    false   - bottom
1812          */
1813         int getSizeForPrimaryAxis(Insets insets, boolean isTop) {
1814             if (axis == 0) {
1815                 if (isTop) {
1816                     return insets.left;
1817                 }
1818                 return insets.right;
1819             }
1820             if (isTop) {
1821                 return insets.top;
1822             }
1823             return insets.bottom;
1824         }
1825 
1826         /**
1827          * Returns a particular value of the inset identified by the
1828          * axis and <code>isTop</code><p>
1829          *   axis isTop
1830          *    0    true    - left
1831          *    0    false   - right
1832          *    1    true    - top
1833          *    1    false   - bottom
1834          */
1835         int getSizeForSecondaryAxis(Insets insets, boolean isTop) {
1836             if (axis == 0) {
1837                 if (isTop) {
1838                     return insets.top;
1839                 }
1840                 return insets.bottom;
1841             }
1842             if (isTop) {
1843                 return insets.left;
1844             }
1845             return insets.right;
1846         }
1847 
1848         /**
1849          * Determines the components. This should be called whenever
1850          * a new instance of this is installed into an existing
1851          * SplitPane.
1852          */
1853         protected void updateComponents() {
1854             Component comp;
1855 
1856             comp = splitPane.getLeftComponent();
1857             if(components[0] != comp) {
1858                 components[0] = comp;
1859                 if(comp == null) {
1860                     sizes[0] = 0;
1861                 } else {
1862                     sizes[0] = -1;
1863                 }
1864             }
1865 
1866             comp = splitPane.getRightComponent();
1867             if(components[1] != comp) {
1868                 components[1] = comp;
1869                 if(comp == null) {
1870                     sizes[1] = 0;
1871                 } else {
1872                     sizes[1] = -1;
1873                 }
1874             }
1875 
1876             /* Find the divider. */
1877             Component[] children = splitPane.getComponents();
1878             Component   oldDivider = components[2];
1879 
1880             components[2] = null;
1881             for(int counter = children.length - 1; counter >= 0; counter--) {
1882                 if(children[counter] != components[0] &&
1883                    children[counter] != components[1] &&
1884                    children[counter] != nonContinuousLayoutDivider) {
1885                     if(oldDivider != children[counter]) {
1886                         components[2] = children[counter];
1887                     } else {
1888                         components[2] = oldDivider;
1889                     }
1890                     break;
1891                 }
1892             }
1893             if(components[2] == null) {
1894                 sizes[2] = 0;
1895             }
1896             else {
1897                 sizes[2] = getSizeForPrimaryAxis(components[2].getPreferredSize());
1898             }
1899         }
1900 
1901         /**
1902          * Resets the size of the first component to <code>leftSize</code>,
1903          * and the right component to the remainder of the space.
1904          */
1905         void setDividerLocation(int leftSize, int availableSize) {
1906             boolean          lValid = (components[0] != null &&
1907                                        components[0].isVisible());
1908             boolean          rValid = (components[1] != null &&
1909                                        components[1].isVisible());
1910             boolean          dValid = (components[2] != null &&
1911                                        components[2].isVisible());
1912             int              max = availableSize;
1913 
1914             if (dValid) {
1915                 max -= sizes[2];
1916             }
1917             leftSize = Math.max(0, Math.min(leftSize, max));
1918             if (lValid) {
1919                 if (rValid) {
1920                     sizes[0] = leftSize;
1921                     sizes[1] = max - leftSize;
1922                 }
1923                 else {
1924                     sizes[0] = max;
1925                     sizes[1] = 0;
1926                 }
1927             }
1928             else if (rValid) {
1929                 sizes[1] = max;
1930                 sizes[0] = 0;
1931             }
1932         }
1933 
1934         /**
1935          * Returns an array of the minimum sizes of the components.
1936          */
1937         int[] getPreferredSizes() {
1938             int[]         retValue = new int[3];
1939 
1940             for (int counter = 0; counter < 3; counter++) {
1941                 if (components[counter] != null &&
1942                     components[counter].isVisible()) {
1943                     retValue[counter] = getPreferredSizeOfComponent
1944                                         (components[counter]);
1945                 }
1946                 else {
1947                     retValue[counter] = -1;
1948                 }
1949             }
1950             return retValue;
1951         }
1952 
1953         /**
1954          * Returns an array of the minimum sizes of the components.
1955          */
1956         int[] getMinimumSizes() {
1957             int[]         retValue = new int[3];
1958 
1959             for (int counter = 0; counter < 2; counter++) {
1960                 if (components[counter] != null &&
1961                     components[counter].isVisible()) {
1962                     retValue[counter] = getMinimumSizeOfComponent
1963                                         (components[counter]);
1964                 }
1965                 else {
1966                     retValue[counter] = -1;
1967                 }
1968             }
1969             retValue[2] = (components[2] != null) ?
1970                 getMinimumSizeOfComponent(components[2]) : -1;
1971             return retValue;
1972         }
1973 
1974         /**
1975          * Resets the components to their preferred sizes.
1976          */
1977         void resetToPreferredSizes(int availableSize) {
1978             // Set the sizes to the preferred sizes (if fits), otherwise
1979             // set to min sizes and distribute any extra space.
1980             int[]       testSizes = getPreferredSizes();
1981             int         totalSize = 0;
1982 
1983             for (int counter = 0; counter < 3; counter++) {
1984                 if (testSizes[counter] != -1) {
1985                     totalSize += testSizes[counter];
1986                 }
1987             }
1988             if (totalSize > availableSize) {
1989                 testSizes = getMinimumSizes();
1990 
1991                 totalSize = 0;
1992                 for (int counter = 0; counter < 3; counter++) {
1993                     if (testSizes[counter] != -1) {
1994                         totalSize += testSizes[counter];
1995                     }
1996                 }
1997             }
1998             setSizes(testSizes);
1999             distributeSpace(availableSize - totalSize, false);
2000         }
2001 
2002         /**
2003          * Distributes <code>space</code> between the two components
2004          * (divider won't get any extra space) based on the weighting. This
2005          * attempts to honor the min size of the components.
2006          *
2007          * @param keepHidden if true and one of the components is 0x0
2008          *                   it gets none of the extra space
2009          */
2010         void distributeSpace(int space, boolean keepHidden) {
2011             boolean          lValid = (components[0] != null &&
2012                                        components[0].isVisible());
2013             boolean          rValid = (components[1] != null &&
2014                                        components[1].isVisible());
2015 
2016             if (keepHidden) {
2017                 if (lValid && getSizeForPrimaryAxis(
2018                                  components[0].getSize()) == 0) {
2019                     lValid = false;
2020                     if (rValid && getSizeForPrimaryAxis(
2021                                      components[1].getSize()) == 0) {
2022                         // Both aren't valid, force them both to be valid
2023                         lValid = true;
2024                     }
2025                 }
2026                 else if (rValid && getSizeForPrimaryAxis(
2027                                    components[1].getSize()) == 0) {
2028                     rValid = false;
2029                 }
2030             }
2031             if (lValid && rValid) {
2032                 double        weight = splitPane.getResizeWeight();
2033                 int           lExtra = (int)(weight * (double)space);
2034                 int           rExtra = (space - lExtra);
2035 
2036                 sizes[0] += lExtra;
2037                 sizes[1] += rExtra;
2038 
2039                 int           lMin = getMinimumSizeOfComponent(components[0]);
2040                 int           rMin = getMinimumSizeOfComponent(components[1]);
2041                 boolean       lMinValid = (sizes[0] >= lMin);
2042                 boolean       rMinValid = (sizes[1] >= rMin);
2043 
2044                 if (!lMinValid && !rMinValid) {
2045                     if (sizes[0] < 0) {
2046                         sizes[1] += sizes[0];
2047                         sizes[0] = 0;
2048                     }
2049                     else if (sizes[1] < 0) {
2050                         sizes[0] += sizes[1];
2051                         sizes[1] = 0;
2052                     }
2053                 }
2054                 else if (!lMinValid) {
2055                     if (sizes[1] - (lMin - sizes[0]) < rMin) {
2056                         // both below min, just make sure > 0
2057                         if (sizes[0] < 0) {
2058                             sizes[1] += sizes[0];
2059                             sizes[0] = 0;
2060                         }
2061                     }
2062                     else {
2063                         sizes[1] -= (lMin - sizes[0]);
2064                         sizes[0] = lMin;
2065                     }
2066                 }
2067                 else if (!rMinValid) {
2068                     if (sizes[0] - (rMin - sizes[1]) < lMin) {
2069                         // both below min, just make sure > 0
2070                         if (sizes[1] < 0) {
2071                             sizes[0] += sizes[1];
2072                             sizes[1] = 0;
2073                         }
2074                     }
2075                     else {
2076                         sizes[0] -= (rMin - sizes[1]);
2077                         sizes[1] = rMin;
2078                     }
2079                 }
2080                 if (sizes[0] < 0) {
2081                     sizes[0] = 0;
2082                 }
2083                 if (sizes[1] < 0) {
2084                     sizes[1] = 0;
2085                 }
2086             }
2087             else if (lValid) {
2088                 sizes[0] = Math.max(0, sizes[0] + space);
2089             }
2090             else if (rValid) {
2091                 sizes[1] = Math.max(0, sizes[1] + space);
2092             }
2093         }
2094     }
2095 
2096 
2097     /**
2098      * LayoutManager used for JSplitPanes with an orientation of
2099      * VERTICAL_SPLIT.
2100      *
2101      */
2102     public class BasicVerticalLayoutManager extends
2103             BasicHorizontalLayoutManager
2104     {
2105         /**
2106          * Constructs a new instance of {@code BasicVerticalLayoutManager}.
2107          */
2108         public BasicVerticalLayoutManager() {
2109             super(1);
2110         }
2111     }
2112 
2113 
2114     private class Handler implements FocusListener, PropertyChangeListener {
2115         //
2116         // PropertyChangeListener
2117         //
2118         /**
2119          * Messaged from the <code>JSplitPane</code> the receiver is
2120          * contained in.  May potentially reset the layout manager and cause a
2121          * <code>validate</code> to be sent.
2122          */
2123         public void propertyChange(PropertyChangeEvent e) {
2124             if(e.getSource() == splitPane) {
2125                 String changeName = e.getPropertyName();
2126 
2127                 if(changeName == JSplitPane.ORIENTATION_PROPERTY) {
2128                     orientation = splitPane.getOrientation();
2129                     resetLayoutManager();
2130                 } else if(changeName == JSplitPane.CONTINUOUS_LAYOUT_PROPERTY){
2131                     setContinuousLayout(splitPane.isContinuousLayout());
2132                     if(!isContinuousLayout()) {
2133                         if(nonContinuousLayoutDivider == null) {
2134                             setNonContinuousLayoutDivider(
2135                                 createDefaultNonContinuousLayoutDivider(),
2136                                 true);
2137                         } else if(nonContinuousLayoutDivider.getParent() ==
2138                                   null) {
2139                             setNonContinuousLayoutDivider(
2140                                 nonContinuousLayoutDivider,
2141                                 true);
2142                         }
2143                     }
2144                 } else if(changeName == JSplitPane.DIVIDER_SIZE_PROPERTY){
2145                     divider.setDividerSize(splitPane.getDividerSize());
2146                     dividerSize = divider.getDividerSize();
2147                     splitPane.revalidate();
2148                     splitPane.repaint();
2149                 }
2150             }
2151         }
2152 
2153         //
2154         // FocusListener
2155         //
2156         public void focusGained(FocusEvent ev) {
2157             dividerKeyboardResize = true;
2158             splitPane.repaint();
2159         }
2160 
2161         public void focusLost(FocusEvent ev) {
2162             dividerKeyboardResize = false;
2163             splitPane.repaint();
2164         }
2165     }
2166 
2167 
2168     private static class Actions extends UIAction {
2169         private static final String NEGATIVE_INCREMENT = "negativeIncrement";
2170         private static final String POSITIVE_INCREMENT = "positiveIncrement";
2171         private static final String SELECT_MIN = "selectMin";
2172         private static final String SELECT_MAX = "selectMax";
2173         private static final String START_RESIZE = "startResize";
2174         private static final String TOGGLE_FOCUS = "toggleFocus";
2175         private static final String FOCUS_OUT_FORWARD = "focusOutForward";
2176         private static final String FOCUS_OUT_BACKWARD = "focusOutBackward";
2177 
2178         Actions(String key) {
2179             super(key);
2180         }
2181 
2182         public void actionPerformed(ActionEvent ev) {
2183             JSplitPane splitPane = (JSplitPane)ev.getSource();
2184             BasicSplitPaneUI ui = (BasicSplitPaneUI)BasicLookAndFeel.
2185                       getUIOfType(splitPane.getUI(), BasicSplitPaneUI.class);
2186 
2187             if (ui == null) {
2188                 return;
2189             }
2190             String key = getName();
2191             if (key == NEGATIVE_INCREMENT) {
2192                 if (ui.dividerKeyboardResize) {
2193                     splitPane.setDividerLocation(Math.max(
2194                               0, ui.getDividerLocation
2195                               (splitPane) - ui.getKeyboardMoveIncrement()));
2196                 }
2197             }
2198             else if (key == POSITIVE_INCREMENT) {
2199                 if (ui.dividerKeyboardResize) {
2200                     splitPane.setDividerLocation(
2201                         ui.getDividerLocation(splitPane) +
2202                         ui.getKeyboardMoveIncrement());
2203                 }
2204             }
2205             else if (key == SELECT_MIN) {
2206                 if (ui.dividerKeyboardResize) {
2207                     splitPane.setDividerLocation(0);
2208                 }
2209             }
2210             else if (key == SELECT_MAX) {
2211                 if (ui.dividerKeyboardResize) {
2212                     Insets   insets = splitPane.getInsets();
2213                     int      bottomI = (insets != null) ? insets.bottom : 0;
2214                     int      rightI = (insets != null) ? insets.right : 0;
2215 
2216                     if (ui.orientation == JSplitPane.VERTICAL_SPLIT) {
2217                         splitPane.setDividerLocation(splitPane.getHeight() -
2218                                                      bottomI);
2219                     }
2220                     else {
2221                         splitPane.setDividerLocation(splitPane.getWidth() -
2222                                                      rightI);
2223                     }
2224                 }
2225             }
2226             else if (key == START_RESIZE) {
2227                 if (!ui.dividerKeyboardResize) {
2228                     splitPane.requestFocus();
2229                 } else {
2230                     JSplitPane parentSplitPane =
2231                         (JSplitPane)SwingUtilities.getAncestorOfClass(
2232                                          JSplitPane.class, splitPane);
2233                     if (parentSplitPane!=null) {
2234                         parentSplitPane.requestFocus();
2235                     }
2236                 }
2237             }
2238             else if (key == TOGGLE_FOCUS) {
2239                 toggleFocus(splitPane);
2240             }
2241             else if (key == FOCUS_OUT_FORWARD) {
2242                 moveFocus(splitPane, 1);
2243             }
2244             else if (key == FOCUS_OUT_BACKWARD) {
2245                 moveFocus(splitPane, -1);
2246             }
2247         }
2248 
2249         private void moveFocus(JSplitPane splitPane, int direction) {
2250             Container rootAncestor = splitPane.getFocusCycleRootAncestor();
2251             FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy();
2252             Component focusOn = (direction > 0) ?
2253                 policy.getComponentAfter(rootAncestor, splitPane) :
2254                 policy.getComponentBefore(rootAncestor, splitPane);
2255             HashSet<Component> focusFrom = new HashSet<Component>();
2256             if (splitPane.isAncestorOf(focusOn)) {
2257                 do {
2258                     focusFrom.add(focusOn);
2259                     rootAncestor = focusOn.getFocusCycleRootAncestor();
2260                     policy = rootAncestor.getFocusTraversalPolicy();
2261                     focusOn = (direction > 0) ?
2262                         policy.getComponentAfter(rootAncestor, focusOn) :
2263                         policy.getComponentBefore(rootAncestor, focusOn);
2264                 } while (splitPane.isAncestorOf(focusOn) &&
2265                          !focusFrom.contains(focusOn));
2266             }
2267             if ( focusOn!=null && !splitPane.isAncestorOf(focusOn) ) {
2268                 focusOn.requestFocus();
2269             }
2270         }
2271 
2272         private void toggleFocus(JSplitPane splitPane) {
2273             Component left = splitPane.getLeftComponent();
2274             Component right = splitPane.getRightComponent();
2275 
2276             KeyboardFocusManager manager =
2277                 KeyboardFocusManager.getCurrentKeyboardFocusManager();
2278             Component focus = manager.getFocusOwner();
2279             Component focusOn = getNextSide(splitPane, focus);
2280             if (focusOn != null) {
2281                 // don't change the focus if the new focused component belongs
2282                 // to the same splitpane and the same side
2283                 if ( focus!=null &&
2284                      ( (SwingUtilities.isDescendingFrom(focus, left) &&
2285                         SwingUtilities.isDescendingFrom(focusOn, left)) ||
2286                        (SwingUtilities.isDescendingFrom(focus, right) &&
2287                         SwingUtilities.isDescendingFrom(focusOn, right)) ) ) {
2288                     return;
2289                 }
2290                 SwingUtilities2.compositeRequestFocus(focusOn);
2291             }
2292         }
2293 
2294         private Component getNextSide(JSplitPane splitPane, Component focus) {
2295             Component left = splitPane.getLeftComponent();
2296             Component right = splitPane.getRightComponent();
2297             Component next;
2298             if (focus!=null && SwingUtilities.isDescendingFrom(focus, left) &&
2299                 right!=null) {
2300                 next = getFirstAvailableComponent(right);
2301                 if (next != null) {
2302                     return next;
2303                 }
2304             }
2305             JSplitPane parentSplitPane = (JSplitPane)SwingUtilities.getAncestorOfClass(JSplitPane.class, splitPane);
2306             if (parentSplitPane!=null) {
2307                 // focus next side of the parent split pane
2308                 next = getNextSide(parentSplitPane, focus);
2309             } else {
2310                 next = getFirstAvailableComponent(left);
2311                 if (next == null) {
2312                     next = getFirstAvailableComponent(right);
2313                 }
2314             }
2315             return next;
2316         }
2317 
2318         private Component getFirstAvailableComponent(Component c) {
2319             if (c!=null && c instanceof JSplitPane) {
2320                 JSplitPane sp = (JSplitPane)c;
2321                 Component left = getFirstAvailableComponent(sp.getLeftComponent());
2322                 if (left != null) {
2323                     c = left;
2324                 } else {
2325                     c = getFirstAvailableComponent(sp.getRightComponent());
2326                 }
2327             }
2328             return c;
2329         }
2330     }
2331 }