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