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