001 /* BasicSplitPaneUI.java -- 002 Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.plaf.basic; 040 041 import java.awt.Canvas; 042 import java.awt.Color; 043 import java.awt.Component; 044 import java.awt.Container; 045 import java.awt.Dimension; 046 import java.awt.Graphics; 047 import java.awt.Insets; 048 import java.awt.LayoutManager2; 049 import java.awt.Point; 050 import java.awt.event.ActionEvent; 051 import java.awt.event.ActionListener; 052 import java.awt.event.FocusAdapter; 053 import java.awt.event.FocusEvent; 054 import java.awt.event.FocusListener; 055 import java.beans.PropertyChangeEvent; 056 import java.beans.PropertyChangeListener; 057 058 import javax.swing.AbstractAction; 059 import javax.swing.ActionMap; 060 import javax.swing.InputMap; 061 import javax.swing.JComponent; 062 import javax.swing.JSlider; 063 import javax.swing.JSplitPane; 064 import javax.swing.KeyStroke; 065 import javax.swing.LookAndFeel; 066 import javax.swing.SwingConstants; 067 import javax.swing.SwingUtilities; 068 import javax.swing.UIManager; 069 import javax.swing.plaf.ActionMapUIResource; 070 import javax.swing.plaf.ComponentUI; 071 import javax.swing.plaf.SplitPaneUI; 072 import javax.swing.plaf.UIResource; 073 074 /** 075 * This is the Basic Look and Feel implementation of the SplitPaneUI class. 076 */ 077 public class BasicSplitPaneUI extends SplitPaneUI 078 { 079 /** 080 * This Layout Manager controls the position and size of the components when 081 * the JSplitPane's orientation is HORIZONTAL_SPLIT. 082 * 083 * @specnote Apparently this class was intended to be protected, 084 * but was made public by a compiler bug and is now 085 * public for compatibility. 086 */ 087 public class BasicHorizontalLayoutManager implements LayoutManager2 088 { 089 // 3 components at a time. 090 // LEFT/TOP = 0 091 // RIGHT/BOTTOM = 1 092 // DIVIDER = 2 093 094 /** 095 * This array contains the components in the JSplitPane. The left/top 096 * component is at index 0, the right/bottom is at 1, and the divider is 097 * at 2. 098 */ 099 protected Component[] components = new Component[3]; 100 101 // These are the _current_ widths of the associated component. 102 103 /** 104 * This array contains the current width (for HORIZONTAL_SPLIT) or height 105 * (for VERTICAL_SPLIT) of the components. The indices are the same as 106 * for components. 107 */ 108 protected int[] sizes = new int[3]; 109 110 /** 111 * This is used to determine if we are vertical or horizontal layout. 112 * In the JDK, the BasicVerticalLayoutManager seems to have no more 113 * methods implemented (as of JDK5), so we keep this state here. 114 */ 115 private int axis; 116 117 /** 118 * Creates a new instance. This is package private because the reference 119 * implementation has no public constructor either. Still, we need to 120 * call it from BasicVerticalLayoutManager. 121 */ 122 BasicHorizontalLayoutManager() 123 { 124 this(SwingConstants.HORIZONTAL); 125 } 126 127 /** 128 * Creates a new instance for a specified axis. This is provided for 129 * compatibility, since the BasicVerticalLayoutManager seems to have 130 * no more implementation in the RI, according to the specs. So 131 * we handle all the axis specific stuff here. 132 * 133 * @param a the axis, either SwingConstants#HORIZONTAL, 134 * or SwingConstants#VERTICAL 135 */ 136 BasicHorizontalLayoutManager(int a) 137 { 138 axis = a; 139 } 140 141 /** 142 * This method adds the component given to the JSplitPane. The position of 143 * the component is given by the constraints object. 144 * 145 * @param comp The Component to add. 146 * @param constraints The constraints that bind the object. 147 */ 148 public void addLayoutComponent(Component comp, Object constraints) 149 { 150 addLayoutComponent((String) constraints, comp); 151 } 152 153 /** 154 * This method is called to add a Component to the JSplitPane. The 155 * placement string determines where the Component will be placed. The 156 * string should be one of LEFT, RIGHT, TOP, BOTTOM or null (signals that 157 * the component is the divider). 158 * 159 * @param place The placement of the Component. 160 * @param component The Component to add. 161 * 162 * @throws IllegalArgumentException DOCUMENT ME! 163 */ 164 public void addLayoutComponent(String place, Component component) 165 { 166 int i = 0; 167 if (place == null) 168 i = 2; 169 else if (place.equals(JSplitPane.TOP) || place.equals(JSplitPane.LEFT)) 170 i = 0; 171 else if (place.equals(JSplitPane.BOTTOM) 172 || place.equals(JSplitPane.RIGHT)) 173 i = 1; 174 else 175 throw new IllegalArgumentException("Illegal placement in JSplitPane"); 176 components[i] = component; 177 resetSizeAt(i); 178 splitPane.revalidate(); 179 splitPane.repaint(); 180 } 181 182 /** 183 * This method returns the width of the JSplitPane minus the insets. 184 * 185 * @param containerSize The Dimensions of the JSplitPane. 186 * @param insets The Insets of the JSplitPane. 187 * 188 * @return The width of the JSplitPane minus the insets. 189 */ 190 protected int getAvailableSize(Dimension containerSize, Insets insets) 191 { 192 int size; 193 if (axis == SwingConstants.HORIZONTAL) 194 size = containerSize.width - insets.left - insets.right; 195 else 196 size = containerSize.height - insets.top - insets.bottom; 197 return size; 198 } 199 200 /** 201 * This method returns the given insets left value. If the given inset is 202 * null, then 0 is returned. 203 * 204 * @param insets The Insets to use with the JSplitPane. 205 * 206 * @return The inset's left value. 207 */ 208 protected int getInitialLocation(Insets insets) 209 { 210 int loc = 0; 211 if (insets != null) 212 { 213 if (axis == SwingConstants.HORIZONTAL) 214 loc = insets.left; 215 else 216 loc = insets.top; 217 } 218 return loc; 219 } 220 221 /** 222 * This specifies how a component is aligned with respect to other 223 * components in the x fdirection. 224 * 225 * @param target The container. 226 * 227 * @return The component's alignment. 228 */ 229 public float getLayoutAlignmentX(Container target) 230 { 231 return 0.0f; 232 } 233 234 /** 235 * This specifies how a component is aligned with respect to other 236 * components in the y direction. 237 * 238 * @param target The container. 239 * 240 * @return The component's alignment. 241 */ 242 public float getLayoutAlignmentY(Container target) 243 { 244 return 0.0f; 245 } 246 247 /** 248 * This method returns the preferred width of the component. 249 * 250 * @param c The component to measure. 251 * 252 * @return The preferred width of the component. 253 */ 254 protected int getPreferredSizeOfComponent(Component c) 255 { 256 int size = 0; 257 Dimension dims = c.getPreferredSize(); 258 if (axis == SwingConstants.HORIZONTAL) 259 { 260 if (dims != null) 261 size = dims.width; 262 } 263 else 264 { 265 if (dims != null) 266 size = dims.height; 267 } 268 return size; 269 } 270 271 /** 272 * This method returns the current width of the component. 273 * 274 * @param c The component to measure. 275 * 276 * @return The width of the component. 277 */ 278 protected int getSizeOfComponent(Component c) 279 { 280 int size; 281 if (axis == SwingConstants.HORIZONTAL) 282 size = c.getHeight(); 283 else 284 size = c.getWidth(); 285 return size; 286 } 287 288 /** 289 * This method returns the sizes array. 290 * 291 * @return The sizes array. 292 */ 293 protected int[] getSizes() 294 { 295 return sizes; 296 } 297 298 /** 299 * This method invalidates the layout. It does nothing. 300 * 301 * @param c The container to invalidate. 302 */ 303 public void invalidateLayout(Container c) 304 { 305 // DO NOTHING 306 } 307 308 /** 309 * This method lays out the components in the container. 310 * 311 * @param container The container to lay out. 312 */ 313 public void layoutContainer(Container container) 314 { 315 if (container instanceof JSplitPane) 316 { 317 JSplitPane split = (JSplitPane) container; 318 distributeExtraSpace(); 319 Insets insets = split.getInsets(); 320 Dimension dims = split.getSize(); 321 int loc = getInitialLocation(insets); 322 int available = getAvailableSize(dims, insets); 323 sizes[0] = split.getDividerLocation(); 324 sizes[1] = available - sizes[0] - sizes[2]; 325 326 // According to a Mauve test we only honour the minimum 327 // size of the components, when the dividerLocation hasn't 328 // been excplicitly set. 329 if (! dividerLocationSet) 330 { 331 sizes[0] = Math.max(sizes[0], minimumSizeOfComponent(0)); 332 sizes[1] = Math.max(sizes[1], minimumSizeOfComponent(1)); 333 } 334 // The size of the divider won't change. 335 336 // Layout component#1. 337 setComponentToSize(components[0], sizes[0], loc, insets, dims); 338 // Layout divider. 339 loc += sizes[0]; 340 setComponentToSize(components[2], sizes[2], loc, insets, dims); 341 // Layout component#2. 342 loc += sizes[2]; 343 setComponentToSize(components[1], sizes[1], loc, insets, dims); 344 } 345 } 346 347 /** 348 * This method returns the maximum size for the container given the 349 * components. It returns a new Dimension object that has width and 350 * height equal to Integer.MAX_VALUE. 351 * 352 * @param target The container to measure. 353 * 354 * @return The maximum size. 355 */ 356 public Dimension maximumLayoutSize(Container target) 357 { 358 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); 359 } 360 361 /** 362 * This method returns the container's minimum size. The minimum width is 363 * the sum of all the component's minimum widths. The minimum height is 364 * the maximum of all the components' minimum heights. 365 * 366 * @param target The container to measure. 367 * 368 * @return The minimum size. 369 */ 370 public Dimension minimumLayoutSize(Container target) 371 { 372 Dimension dim = new Dimension(); 373 if (target instanceof JSplitPane) 374 { 375 int primary = 0; 376 int secondary = 0; 377 for (int i = 0; i < components.length; i++) 378 { 379 if (components[i] != null) 380 { 381 Dimension dims = components[i].getMinimumSize(); 382 primary += axis == SwingConstants.HORIZONTAL ? dims.width 383 : dims.height; 384 int sec = axis == SwingConstants.HORIZONTAL ? dims.height 385 : dims.width; 386 secondary = Math.max(sec, secondary); 387 } 388 } 389 int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; 390 int height = axis == SwingConstants.VERTICAL ? secondary : primary; 391 392 Insets i = splitPane.getInsets(); 393 dim.setSize(width + i.left + i.right, height + i.top + i.bottom); 394 } 395 return dim; 396 } 397 398 /** 399 * This method returns the container's preferred size. The preferred width 400 * is the sum of all the component's preferred widths. The preferred 401 * height is the maximum of all the components' preferred heights. 402 * 403 * @param target The container to measure. 404 * 405 * @return The preferred size. 406 */ 407 public Dimension preferredLayoutSize(Container target) 408 { 409 Dimension dim = new Dimension(); 410 if (target instanceof JSplitPane) 411 { 412 int primary = 0; 413 int secondary = 0; 414 for (int i = 0; i < components.length; i++) 415 { 416 if (components[i] != null) 417 { 418 Dimension dims = components[i].getPreferredSize(); 419 primary += axis == SwingConstants.HORIZONTAL ? dims.width 420 : dims.height; 421 int sec = axis == SwingConstants.HORIZONTAL ? dims.height 422 : dims.width; 423 secondary = Math.max(sec, secondary); 424 } 425 } 426 int width = axis == SwingConstants.HORIZONTAL ? primary : secondary; 427 int height = axis == SwingConstants.VERTICAL ? secondary : primary; 428 429 Insets i = splitPane.getInsets(); 430 dim.setSize(width + i.left + i.right, height + i.top + i.bottom); 431 } 432 return dim; 433 } 434 435 /** 436 * This method removes the component from the layout. 437 * 438 * @param component The component to remove from the layout. 439 */ 440 public void removeLayoutComponent(Component component) 441 { 442 for (int i = 0; i < components.length; i++) 443 { 444 if (component == components[i]) 445 { 446 components[i] = null; 447 sizes[i] = 0; 448 } 449 } 450 } 451 452 /** 453 * This method resets the size of Component to the preferred size. 454 * 455 * @param index The index of the component to reset. 456 */ 457 protected void resetSizeAt(int index) 458 { 459 if (components[index] != null) 460 sizes[index] = getPreferredSizeOfComponent(components[index]); 461 } 462 463 /** 464 * This method resets the sizes of all the components. 465 */ 466 public void resetToPreferredSizes() 467 { 468 for (int i = 0; i < components.length; i++) 469 resetSizeAt(i); 470 } 471 472 /** 473 * This methods sets the bounds of the given component. The width is the 474 * size. The height is the container size minus the top and bottom 475 * inset. The x coordinate is the location given. The y coordinate is 476 * the top inset. 477 * 478 * @param c The component to set. 479 * @param size The width of the component. 480 * @param location The x coordinate. 481 * @param insets The insets to use. 482 * @param containerSize The height of the container. 483 */ 484 protected void setComponentToSize(Component c, int size, int location, 485 Insets insets, Dimension containerSize) 486 { 487 if (insets != null) 488 { 489 if (axis == SwingConstants.HORIZONTAL) 490 c.setBounds(location, insets.top, size, 491 containerSize.height - insets.top - insets.bottom); 492 else 493 c.setBounds(insets.left, location, 494 containerSize.width - insets.left - insets.right, 495 size); 496 } 497 else 498 { 499 if (axis == SwingConstants.HORIZONTAL) 500 c.setBounds(location, 0, size, containerSize.height); 501 else 502 c.setBounds(0, location, containerSize.width, size); 503 } 504 } 505 506 /** 507 * This method stores the given int array as the new sizes array. 508 * 509 * @param newSizes The array to use as sizes. 510 */ 511 protected void setSizes(int[] newSizes) 512 { 513 sizes = newSizes; 514 } 515 516 /** 517 * This method determines the size of each component. It should be called 518 * when a new Layout Manager is created for an existing JSplitPane. 519 */ 520 protected void updateComponents() 521 { 522 Component left = splitPane.getLeftComponent(); 523 Component right = splitPane.getRightComponent(); 524 525 if (left != null) 526 { 527 components[0] = left; 528 resetSizeAt(0); 529 } 530 if (right != null) 531 { 532 components[1] = right; 533 resetSizeAt(1); 534 } 535 components[2] = divider; 536 } 537 538 /** 539 * This method resizes the left and right components to fit inside the 540 * JSplitPane when there is extra space. 541 */ 542 void distributeExtraSpace() 543 { 544 // FIXME: This needs to be reimplemented correctly. 545 } 546 547 /** 548 * This method returns the minimum width of the component at the given 549 * index. 550 * 551 * @param index The index to check. 552 * 553 * @return The minimum width. 554 */ 555 int minimumSizeOfComponent(int index) 556 { 557 Dimension dims = components[index].getMinimumSize(); 558 int size = 0; 559 if (dims != null) 560 if (axis == SwingConstants.HORIZONTAL) 561 size = dims.width; 562 else 563 size = dims.height; 564 return size; 565 } 566 } //end BasicHorizontalLayoutManager 567 568 /** 569 * This class is the Layout Manager for the JSplitPane when the orientation 570 * is VERTICAL_SPLIT. 571 * 572 * @specnote Apparently this class was intended to be protected, 573 * but was made public by a compiler bug and is now 574 * public for compatibility. 575 */ 576 public class BasicVerticalLayoutManager 577 extends BasicHorizontalLayoutManager 578 { 579 /** 580 * Creates a new instance. 581 */ 582 public BasicVerticalLayoutManager() 583 { 584 super(SwingConstants.VERTICAL); 585 } 586 } 587 588 /** 589 * This class handles FocusEvents from the JComponent. 590 * 591 * @specnote Apparently this class was intended to be protected, 592 * but was made public by a compiler bug and is now 593 * public for compatibility. 594 */ 595 public class FocusHandler extends FocusAdapter 596 { 597 /** 598 * This method is called when the JSplitPane gains focus. 599 * 600 * @param ev The FocusEvent. 601 */ 602 public void focusGained(FocusEvent ev) 603 { 604 // repaint the divider because its background color may change due to 605 // the focus state... 606 divider.repaint(); 607 } 608 609 /** 610 * This method is called when the JSplitPane loses focus. 611 * 612 * @param ev The FocusEvent. 613 */ 614 public void focusLost(FocusEvent ev) 615 { 616 // repaint the divider because its background color may change due to 617 // the focus state... 618 divider.repaint(); 619 } 620 } 621 622 /** 623 * This is a deprecated class. It is supposed to be used for handling down 624 * and right key presses. 625 * 626 * @specnote Apparently this class was intended to be protected, 627 * but was made public by a compiler bug and is now 628 * public for compatibility. 629 */ 630 public class KeyboardDownRightHandler implements ActionListener 631 { 632 /** 633 * This method is called when the down or right keys are pressed. 634 * 635 * @param ev The ActionEvent 636 */ 637 public void actionPerformed(ActionEvent ev) 638 { 639 // FIXME: implement. 640 } 641 } 642 643 /** 644 * This is a deprecated class. It is supposed to be used for handling end 645 * key presses. 646 * 647 * @specnote Apparently this class was intended to be protected, 648 * but was made public by a compiler bug and is now 649 * public for compatibility. 650 */ 651 public class KeyboardEndHandler implements ActionListener 652 { 653 /** 654 * This method is called when the end key is pressed. 655 * 656 * @param ev The ActionEvent. 657 */ 658 public void actionPerformed(ActionEvent ev) 659 { 660 // FIXME: implement. 661 } 662 } 663 664 /** 665 * This is a deprecated class. It is supposed to be used for handling home 666 * key presses. 667 * 668 * @specnote Apparently this class was intended to be protected, 669 * but was made public by a compiler bug and is now 670 * public for compatibility. 671 */ 672 public class KeyboardHomeHandler implements ActionListener 673 { 674 /** 675 * This method is called when the home key is pressed. 676 * 677 * @param ev The ActionEvent. 678 */ 679 public void actionPerformed(ActionEvent ev) 680 { 681 // FIXME: implement. 682 } 683 } 684 685 /** 686 * This is a deprecated class. It is supposed to be used for handling resize 687 * toggles. 688 * 689 * @specnote Apparently this class was intended to be protected, 690 * but was made public by a compiler bug and is now 691 * public for compatibility. 692 */ 693 public class KeyboardResizeToggleHandler implements ActionListener 694 { 695 /** 696 * This method is called when a resize is toggled. 697 * 698 * @param ev The ActionEvent. 699 */ 700 public void actionPerformed(ActionEvent ev) 701 { 702 // FIXME: implement. 703 } 704 } 705 706 /** 707 * This is a deprecated class. It is supposed to be used for handler up and 708 * left key presses. 709 * 710 * @specnote Apparently this class was intended to be protected, 711 * but was made public by a compiler bug and is now 712 * public for compatibility. 713 */ 714 public class KeyboardUpLeftHandler implements ActionListener 715 { 716 /** 717 * This method is called when the left or up keys are pressed. 718 * 719 * @param ev The ActionEvent. 720 */ 721 public void actionPerformed(ActionEvent ev) 722 { 723 // FIXME: implement. 724 } 725 } 726 727 /** 728 * This helper class handles PropertyChangeEvents from the JSplitPane. When 729 * a property changes, this will update the UI accordingly. 730 * 731 * @specnote Apparently this class was intended to be protected, 732 * but was made public by a compiler bug and is now 733 * public for compatibility. 734 */ 735 public class PropertyHandler implements PropertyChangeListener 736 { 737 /** 738 * This method is called whenever one of the JSplitPane's properties 739 * change. 740 * 741 * @param e DOCUMENT ME! 742 */ 743 public void propertyChange(PropertyChangeEvent e) 744 { 745 if (e.getPropertyName().equals(JSplitPane.DIVIDER_SIZE_PROPERTY)) 746 { 747 int newSize = splitPane.getDividerSize(); 748 int[] tmpSizes = layoutManager.getSizes(); 749 dividerSize = tmpSizes[2]; 750 int newSpace = newSize - tmpSizes[2]; 751 tmpSizes[2] = newSize; 752 753 tmpSizes[0] += newSpace / 2; 754 tmpSizes[1] += newSpace / 2; 755 756 layoutManager.setSizes(tmpSizes); 757 } 758 else if (e.getPropertyName().equals(JSplitPane.ORIENTATION_PROPERTY)) 759 { 760 int max = layoutManager.getAvailableSize(splitPane.getSize(), 761 splitPane.getInsets()); 762 int dividerLoc = getDividerLocation(splitPane); 763 double prop = ((double) dividerLoc) / max; 764 765 resetLayoutManager(); 766 if (prop <= 1 && prop >= 0) 767 splitPane.setDividerLocation(prop); 768 } 769 // Don't have to deal with continuous_layout - only 770 // necessary in dragging modes (and it's checked 771 // every time you drag there) 772 // Don't have to deal with resize_weight (as there 773 // will be no extra space associated with this 774 // event - the changes to the weighting will 775 // be taken into account the next time the 776 // sizes change.) 777 // Don't have to deal with divider_location 778 // The method in JSplitPane calls our setDividerLocation 779 // so we'll know about those anyway. 780 // Don't have to deal with last_divider_location 781 // Although I'm not sure why, it doesn't seem to 782 // have any effect on Sun's JSplitPane. 783 // one_touch_expandable changes are dealt with 784 // by our divider. 785 } 786 } 787 788 /** The location of the divider when dragging began. */ 789 protected int beginDragDividerLocation; 790 791 /** The size of the divider while dragging. */ 792 protected int dividerSize; 793 794 /** The location where the last drag location ended. */ 795 transient int lastDragLocation = -1; 796 797 /** The distance the divider is moved when moved by keyboard actions. */ 798 // Sun defines this as 3 799 protected static int KEYBOARD_DIVIDER_MOVE_OFFSET = 3; 800 801 /** The divider that divides this JSplitPane. */ 802 protected BasicSplitPaneDivider divider; 803 804 /** The listener that listens for PropertyChangeEvents from the JSplitPane. */ 805 protected PropertyChangeListener propertyChangeListener; 806 807 /** The JSplitPane's focus handler. */ 808 protected FocusListener focusListener; 809 810 /** @deprecated The handler for down and right key presses. */ 811 protected ActionListener keyboardDownRightListener; 812 813 /** @deprecated The handler for end key presses. */ 814 protected ActionListener keyboardEndListener; 815 816 /** @deprecated The handler for home key presses. */ 817 protected ActionListener keyboardHomeListener; 818 819 /** @deprecated The handler for toggling resizes. */ 820 protected ActionListener keyboardResizeToggleListener; 821 822 /** @deprecated The handler for up and left key presses. */ 823 protected ActionListener keyboardUpLeftListener; 824 825 /** The JSplitPane's current layout manager. */ 826 protected BasicHorizontalLayoutManager layoutManager; 827 828 /** @deprecated The divider resize toggle key. */ 829 protected KeyStroke dividerResizeToggleKey; 830 831 /** @deprecated The down key. */ 832 protected KeyStroke downKey; 833 834 /** @deprecated The end key. */ 835 protected KeyStroke endKey; 836 837 /** @deprecated The home key. */ 838 protected KeyStroke homeKey; 839 840 /** @deprecated The left key. */ 841 protected KeyStroke leftKey; 842 843 /** @deprecated The right key. */ 844 protected KeyStroke rightKey; 845 846 /** @deprecated The up key. */ 847 protected KeyStroke upKey; 848 849 /** Set to true when dragging heavy weight components. */ 850 protected boolean draggingHW; 851 852 /** 853 * The constraints object used when adding the non-continuous divider to the 854 * JSplitPane. 855 */ 856 protected static final String NON_CONTINUOUS_DIVIDER 857 = "nonContinuousDivider"; 858 859 /** The dark divider used when dragging in non-continuous layout mode. */ 860 protected Component nonContinuousLayoutDivider; 861 862 /** The JSplitPane that this UI draws. */ 863 protected JSplitPane splitPane; 864 865 /** 866 * True, when setDividerLocation() has been called at least 867 * once on the JSplitPane, false otherwise. 868 * 869 * This is package private to avoid a synthetic accessor method. 870 */ 871 boolean dividerLocationSet; 872 873 /** 874 * Creates a new BasicSplitPaneUI object. 875 */ 876 public BasicSplitPaneUI() 877 { 878 // Nothing to do here. 879 } 880 881 /** 882 * This method creates a new BasicSplitPaneUI for the given JComponent. 883 * 884 * @param x The JComponent to create a UI for. 885 * 886 * @return A new BasicSplitPaneUI. 887 */ 888 public static ComponentUI createUI(JComponent x) 889 { 890 return new BasicSplitPaneUI(); 891 } 892 893 /** 894 * This method installs the BasicSplitPaneUI for the given JComponent. 895 * 896 * @param c The JComponent to install the UI for. 897 */ 898 public void installUI(JComponent c) 899 { 900 if (c instanceof JSplitPane) 901 { 902 splitPane = (JSplitPane) c; 903 dividerLocationSet = false; 904 installDefaults(); 905 installListeners(); 906 installKeyboardActions(); 907 } 908 } 909 910 /** 911 * This method uninstalls the BasicSplitPaneUI for the given JComponent. 912 * 913 * @param c The JComponent to uninstall the UI for. 914 */ 915 public void uninstallUI(JComponent c) 916 { 917 uninstallKeyboardActions(); 918 uninstallListeners(); 919 uninstallDefaults(); 920 921 dividerLocationSet = false; 922 splitPane = null; 923 } 924 925 /** 926 * This method installs the defaults given by the Look and Feel. 927 */ 928 protected void installDefaults() 929 { 930 LookAndFeel.installColors(splitPane, "SplitPane.background", 931 "SplitPane.foreground"); 932 LookAndFeel.installBorder(splitPane, "SplitPane.border"); 933 divider = createDefaultDivider(); 934 divider.setBorder(UIManager.getBorder("SplitPaneDivider.border")); 935 resetLayoutManager(); 936 nonContinuousLayoutDivider = createDefaultNonContinuousLayoutDivider(); 937 splitPane.add(divider, JSplitPane.DIVIDER); 938 939 // There is no need to add the nonContinuousLayoutDivider. 940 dividerSize = UIManager.getInt("SplitPane.dividerSize"); 941 splitPane.setDividerSize(dividerSize); 942 divider.setDividerSize(dividerSize); 943 splitPane.setOpaque(true); 944 } 945 946 /** 947 * This method uninstalls the defaults and nulls any objects created during 948 * install. 949 */ 950 protected void uninstallDefaults() 951 { 952 layoutManager = null; 953 splitPane.remove(divider); 954 divider = null; 955 nonContinuousLayoutDivider = null; 956 957 if (splitPane.getBackground() instanceof UIResource) 958 splitPane.setBackground(null); 959 if (splitPane.getBorder() instanceof UIResource) 960 splitPane.setBorder(null); 961 } 962 963 /** 964 * This method installs the listeners needed for this UI to function. 965 */ 966 protected void installListeners() 967 { 968 propertyChangeListener = createPropertyChangeListener(); 969 focusListener = createFocusListener(); 970 971 splitPane.addPropertyChangeListener(propertyChangeListener); 972 splitPane.addFocusListener(focusListener); 973 } 974 975 /** 976 * This method uninstalls all listeners registered for the UI. 977 */ 978 protected void uninstallListeners() 979 { 980 splitPane.removePropertyChangeListener(propertyChangeListener); 981 splitPane.removeFocusListener(focusListener); 982 983 focusListener = null; 984 propertyChangeListener = null; 985 } 986 987 /** 988 * Returns the input map for the specified condition. 989 * 990 * @param condition the condition. 991 * 992 * @return The input map. 993 */ 994 InputMap getInputMap(int condition) 995 { 996 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) 997 return (InputMap) UIManager.get("SplitPane.ancestorInputMap"); 998 return null; 999 } 1000 1001 /** 1002 * Returns the action map for the {@link JSplitPane}. All sliders share 1003 * a single action map which is created the first time this method is 1004 * called, then stored in the UIDefaults table for subsequent access. 1005 * 1006 * @return The shared action map. 1007 */ 1008 ActionMap getActionMap() 1009 { 1010 ActionMap map = (ActionMap) UIManager.get("SplitPane.actionMap"); 1011 1012 if (map == null) // first time here 1013 { 1014 map = createActionMap(); 1015 if (map != null) 1016 UIManager.put("SplitPane.actionMap", map); 1017 } 1018 return map; 1019 } 1020 1021 /** 1022 * Creates the action map shared by all {@link JSlider} instances. 1023 * This method is called once by {@link #getActionMap()} when it 1024 * finds no action map in the UIDefaults table...after the map is 1025 * created, it gets added to the defaults table so that subsequent 1026 * calls to {@link #getActionMap()} will return the same shared 1027 * instance. 1028 * 1029 * @return The action map. 1030 */ 1031 ActionMap createActionMap() 1032 { 1033 ActionMap map = new ActionMapUIResource(); 1034 map.put("toggleFocus", 1035 new AbstractAction("toggleFocus") { 1036 public void actionPerformed(ActionEvent event) 1037 { 1038 // FIXME: What to do here? 1039 } 1040 } 1041 ); 1042 map.put("startResize", 1043 new AbstractAction("startResize") { 1044 public void actionPerformed(ActionEvent event) 1045 { 1046 splitPane.requestFocus(); 1047 } 1048 } 1049 ); 1050 map.put("selectMax", 1051 new AbstractAction("selectMax") { 1052 public void actionPerformed(ActionEvent event) 1053 { 1054 splitPane.setDividerLocation(1.0); 1055 } 1056 } 1057 ); 1058 map.put("selectMin", 1059 new AbstractAction("selectMin") { 1060 public void actionPerformed(ActionEvent event) 1061 { 1062 splitPane.setDividerLocation(0.0); 1063 } 1064 } 1065 ); 1066 map.put("negativeIncrement", 1067 new AbstractAction("negativeIncrement") { 1068 public void actionPerformed(ActionEvent event) 1069 { 1070 int oldLoc = splitPane.getDividerLocation(); 1071 int newLoc = 1072 Math.max(oldLoc - KEYBOARD_DIVIDER_MOVE_OFFSET, 0); 1073 splitPane.setDividerLocation(newLoc); 1074 } 1075 } 1076 ); 1077 map.put("positiveIncrement", 1078 new AbstractAction("positiveIncrement") { 1079 public void actionPerformed(ActionEvent event) 1080 { 1081 int oldLoc = splitPane.getDividerLocation(); 1082 int newLoc = 1083 Math.max(oldLoc + KEYBOARD_DIVIDER_MOVE_OFFSET, 0); 1084 splitPane.setDividerLocation(newLoc); 1085 } 1086 } 1087 ); 1088 map.put("focusOutBackward", 1089 new AbstractAction("focusOutBackward") { 1090 public void actionPerformed(ActionEvent event) 1091 { 1092 // FIXME: implement this 1093 } 1094 } 1095 ); 1096 map.put("focusOutForward", 1097 new AbstractAction("focusOutForward") { 1098 public void actionPerformed(ActionEvent event) 1099 { 1100 // FIXME: implement this 1101 } 1102 } 1103 ); 1104 return map; 1105 } 1106 1107 /** 1108 * Installs any keyboard actions. The list of keys that need to be bound are 1109 * listed in Basic look and feel's defaults. 1110 */ 1111 protected void installKeyboardActions() 1112 { 1113 InputMap keyMap = getInputMap( 1114 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1115 SwingUtilities.replaceUIInputMap(splitPane, 1116 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap); 1117 ActionMap map = getActionMap(); 1118 SwingUtilities.replaceUIActionMap(splitPane, map); 1119 } 1120 1121 /** 1122 * This method reverses the work done in installKeyboardActions. 1123 */ 1124 protected void uninstallKeyboardActions() 1125 { 1126 SwingUtilities.replaceUIActionMap(splitPane, null); 1127 SwingUtilities.replaceUIInputMap(splitPane, 1128 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 1129 } 1130 1131 /** 1132 * This method creates a new PropertyChangeListener. 1133 * 1134 * @return A new PropertyChangeListener. 1135 */ 1136 protected PropertyChangeListener createPropertyChangeListener() 1137 { 1138 return new PropertyHandler(); 1139 } 1140 1141 /** 1142 * This method creates a new FocusListener. 1143 * 1144 * @return A new FocusListener. 1145 */ 1146 protected FocusListener createFocusListener() 1147 { 1148 return new FocusHandler(); 1149 } 1150 1151 /** 1152 * This method creates a new ActionListener for up and left key presses. 1153 * 1154 * @return A new ActionListener for up and left keys. 1155 * 1156 * @deprecated 1.3 1157 */ 1158 protected ActionListener createKeyboardUpLeftListener() 1159 { 1160 return new KeyboardUpLeftHandler(); 1161 } 1162 1163 /** 1164 * This method creates a new ActionListener for down and right key presses. 1165 * 1166 * @return A new ActionListener for down and right keys. 1167 * 1168 * @deprecated 1.3 1169 */ 1170 protected ActionListener createKeyboardDownRightListener() 1171 { 1172 return new KeyboardDownRightHandler(); 1173 } 1174 1175 /** 1176 * This method creates a new ActionListener for home key presses. 1177 * 1178 * @return A new ActionListener for home keys. 1179 * 1180 * @deprecated 1181 */ 1182 protected ActionListener createKeyboardHomeListener() 1183 { 1184 return new KeyboardHomeHandler(); 1185 } 1186 1187 /** 1188 * This method creates a new ActionListener for end key presses.i 1189 * 1190 * @return A new ActionListener for end keys. 1191 * 1192 * @deprecated 1.3 1193 */ 1194 protected ActionListener createKeyboardEndListener() 1195 { 1196 return new KeyboardEndHandler(); 1197 } 1198 1199 /** 1200 * This method creates a new ActionListener for resize toggle key events. 1201 * 1202 * @return A new ActionListener for resize toggle keys. 1203 * 1204 * @deprecated 1.3 1205 */ 1206 protected ActionListener createKeyboardResizeToggleListener() 1207 { 1208 return new KeyboardResizeToggleHandler(); 1209 } 1210 1211 /** 1212 * This method returns the orientation of the JSplitPane. 1213 * 1214 * @return The orientation of the JSplitPane. 1215 */ 1216 public int getOrientation() 1217 { 1218 return splitPane.getOrientation(); 1219 } 1220 1221 /** 1222 * This method sets the orientation of the JSplitPane. 1223 * 1224 * @param orientation The new orientation of the JSplitPane. 1225 */ 1226 public void setOrientation(int orientation) 1227 { 1228 splitPane.setOrientation(orientation); 1229 } 1230 1231 /** 1232 * This method returns true if the JSplitPane is using continuous layout. 1233 * 1234 * @return True if the JSplitPane is using continuous layout. 1235 */ 1236 public boolean isContinuousLayout() 1237 { 1238 return splitPane.isContinuousLayout(); 1239 } 1240 1241 /** 1242 * This method sets the continuous layout property of the JSplitPane. 1243 * 1244 * @param b True if the JsplitPane is to use continuous layout. 1245 */ 1246 public void setContinuousLayout(boolean b) 1247 { 1248 splitPane.setContinuousLayout(b); 1249 } 1250 1251 /** 1252 * This method returns the last location the divider was dragged to. 1253 * 1254 * @return The last location the divider was dragged to. 1255 */ 1256 public int getLastDragLocation() 1257 { 1258 return lastDragLocation; 1259 } 1260 1261 /** 1262 * This method sets the last location the divider was dragged to. 1263 * 1264 * @param l The last location the divider was dragged to. 1265 */ 1266 public void setLastDragLocation(int l) 1267 { 1268 lastDragLocation = l; 1269 } 1270 1271 /** 1272 * This method returns the BasicSplitPaneDivider that divides this 1273 * JSplitPane. 1274 * 1275 * @return The divider for the JSplitPane. 1276 */ 1277 public BasicSplitPaneDivider getDivider() 1278 { 1279 return divider; 1280 } 1281 1282 /** 1283 * This method creates a nonContinuousLayoutDivider for use with the 1284 * JSplitPane in nonContinousLayout mode. The default divider is a gray 1285 * Canvas. 1286 * 1287 * @return The default nonContinousLayoutDivider. 1288 */ 1289 protected Component createDefaultNonContinuousLayoutDivider() 1290 { 1291 if (nonContinuousLayoutDivider == null) 1292 { 1293 nonContinuousLayoutDivider = new Canvas(); 1294 Color c = UIManager.getColor("SplitPaneDivider.draggingColor"); 1295 nonContinuousLayoutDivider.setBackground(c); 1296 } 1297 return nonContinuousLayoutDivider; 1298 } 1299 1300 /** 1301 * This method sets the component to use as the nonContinuousLayoutDivider. 1302 * 1303 * @param newDivider The component to use as the nonContinuousLayoutDivider. 1304 */ 1305 protected void setNonContinuousLayoutDivider(Component newDivider) 1306 { 1307 setNonContinuousLayoutDivider(newDivider, true); 1308 } 1309 1310 /** 1311 * This method sets the component to use as the nonContinuousLayoutDivider. 1312 * 1313 * @param newDivider The component to use as the nonContinuousLayoutDivider. 1314 * @param rememberSizes FIXME: document. 1315 */ 1316 protected void setNonContinuousLayoutDivider(Component newDivider, 1317 boolean rememberSizes) 1318 { 1319 // FIXME: use rememberSizes for something 1320 nonContinuousLayoutDivider = newDivider; 1321 } 1322 1323 /** 1324 * This method returns the nonContinuousLayoutDivider. 1325 * 1326 * @return The nonContinuousLayoutDivider. 1327 */ 1328 public Component getNonContinuousLayoutDivider() 1329 { 1330 return nonContinuousLayoutDivider; 1331 } 1332 1333 /** 1334 * This method returns the JSplitPane that this BasicSplitPaneUI draws. 1335 * 1336 * @return The JSplitPane. 1337 */ 1338 public JSplitPane getSplitPane() 1339 { 1340 return splitPane; 1341 } 1342 1343 /** 1344 * This method creates the divider used normally with the JSplitPane. 1345 * 1346 * @return The default divider. 1347 */ 1348 public BasicSplitPaneDivider createDefaultDivider() 1349 { 1350 if (divider == null) 1351 divider = new BasicSplitPaneDivider(this); 1352 return divider; 1353 } 1354 1355 /** 1356 * This method is called when JSplitPane's resetToPreferredSizes is called. 1357 * It resets the sizes of all components in the JSplitPane. 1358 * 1359 * @param jc The JSplitPane to reset. 1360 */ 1361 public void resetToPreferredSizes(JSplitPane jc) 1362 { 1363 layoutManager.resetToPreferredSizes(); 1364 } 1365 1366 /** 1367 * This method sets the location of the divider. 1368 * 1369 * @param jc The JSplitPane to set the divider location in. 1370 * @param location The new location of the divider. 1371 */ 1372 public void setDividerLocation(JSplitPane jc, int location) 1373 { 1374 dividerLocationSet = true; 1375 splitPane.revalidate(); 1376 splitPane.repaint(); 1377 } 1378 1379 /** 1380 * This method returns the location of the divider. 1381 * 1382 * @param jc The JSplitPane to retrieve the location for. 1383 * 1384 * @return The location of the divider. 1385 */ 1386 public int getDividerLocation(JSplitPane jc) 1387 { 1388 int loc; 1389 if (jc.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) 1390 loc = divider.getX(); 1391 else 1392 loc = divider.getY(); 1393 return loc; 1394 } 1395 1396 /** 1397 * This method returns the smallest value possible for the location of the 1398 * divider. 1399 * 1400 * @param jc The JSplitPane. 1401 * 1402 * @return The minimum divider location. 1403 */ 1404 public int getMinimumDividerLocation(JSplitPane jc) 1405 { 1406 int value = layoutManager.getInitialLocation(jc.getInsets()); 1407 if (layoutManager.components[0] != null) 1408 value += layoutManager.minimumSizeOfComponent(0); 1409 return value; 1410 } 1411 1412 /** 1413 * This method returns the largest value possible for the location of the 1414 * divider. 1415 * 1416 * @param jc The JSplitPane. 1417 * 1418 * @return The maximum divider location. 1419 */ 1420 public int getMaximumDividerLocation(JSplitPane jc) 1421 { 1422 int value = layoutManager.getInitialLocation(jc.getInsets()) 1423 + layoutManager.getAvailableSize(jc.getSize(), jc.getInsets()) 1424 - splitPane.getDividerSize(); 1425 if (layoutManager.components[1] != null) 1426 value -= layoutManager.minimumSizeOfComponent(1); 1427 return value; 1428 } 1429 1430 /** 1431 * This method is called after the children of the JSplitPane are painted. 1432 * 1433 * @param jc The JSplitPane. 1434 * @param g The Graphics object to paint with. 1435 */ 1436 public void finishedPaintingChildren(JSplitPane jc, Graphics g) 1437 { 1438 if (! splitPane.isContinuousLayout() && nonContinuousLayoutDivider != null 1439 && nonContinuousLayoutDivider.isVisible()) 1440 javax.swing.SwingUtilities.paintComponent(g, nonContinuousLayoutDivider, 1441 null, 1442 nonContinuousLayoutDivider 1443 .getBounds()); 1444 } 1445 1446 /** 1447 * This method is called to paint the JSplitPane. 1448 * 1449 * @param g The Graphics object to paint with. 1450 * @param jc The JSplitPane to paint. 1451 */ 1452 public void paint(Graphics g, JComponent jc) 1453 { 1454 // TODO: What should be done here? 1455 } 1456 1457 /** 1458 * This method returns the preferred size of the JSplitPane. 1459 * 1460 * @param jc The JSplitPane. 1461 * 1462 * @return The preferred size of the JSplitPane. 1463 */ 1464 public Dimension getPreferredSize(JComponent jc) 1465 { 1466 return layoutManager.preferredLayoutSize(jc); 1467 } 1468 1469 /** 1470 * This method returns the minimum size of the JSplitPane. 1471 * 1472 * @param jc The JSplitPane. 1473 * 1474 * @return The minimum size of the JSplitPane. 1475 */ 1476 public Dimension getMinimumSize(JComponent jc) 1477 { 1478 return layoutManager.minimumLayoutSize(jc); 1479 } 1480 1481 /** 1482 * This method returns the maximum size of the JSplitPane. 1483 * 1484 * @param jc The JSplitPane. 1485 * 1486 * @return The maximum size of the JSplitPane. 1487 */ 1488 public Dimension getMaximumSize(JComponent jc) 1489 { 1490 return layoutManager.maximumLayoutSize(jc); 1491 } 1492 1493 /** 1494 * This method returns the border insets of the current border. 1495 * 1496 * @param jc The JSplitPane. 1497 * 1498 * @return The current border insets. 1499 */ 1500 public Insets getInsets(JComponent jc) 1501 { 1502 return splitPane.getBorder().getBorderInsets(splitPane); 1503 } 1504 1505 /** 1506 * This method resets the current layout manager. The type of layout manager 1507 * is dependent on the current orientation. 1508 */ 1509 protected void resetLayoutManager() 1510 { 1511 if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) 1512 layoutManager = new BasicHorizontalLayoutManager(); 1513 else 1514 layoutManager = new BasicVerticalLayoutManager(); 1515 getSplitPane().setLayout(layoutManager); 1516 layoutManager.updateComponents(); 1517 1518 // invalidating by itself does not invalidate the layout. 1519 getSplitPane().revalidate(); 1520 } 1521 1522 /** 1523 * This method is called when dragging starts. It resets lastDragLocation 1524 * and dividerSize. 1525 */ 1526 protected void startDragging() 1527 { 1528 Component left = splitPane.getLeftComponent(); 1529 Component right = splitPane.getRightComponent(); 1530 dividerSize = divider.getDividerSize(); 1531 setLastDragLocation(-1); 1532 1533 if ((left != null && !left.isLightweight()) 1534 || (right != null && !right.isLightweight())) 1535 draggingHW = true; 1536 1537 if (splitPane.isContinuousLayout()) 1538 nonContinuousLayoutDivider.setVisible(false); 1539 else 1540 { 1541 nonContinuousLayoutDivider.setVisible(true); 1542 nonContinuousLayoutDivider.setBounds(divider.getBounds()); 1543 } 1544 } 1545 1546 /** 1547 * This method is called whenever the divider is dragged. If the JSplitPane 1548 * is in continuousLayout mode, the divider needs to be moved and the 1549 * JSplitPane needs to be laid out. 1550 * 1551 * @param location The new location of the divider. 1552 */ 1553 protected void dragDividerTo(int location) 1554 { 1555 location = validLocation(location); 1556 if (beginDragDividerLocation == -1) 1557 beginDragDividerLocation = location; 1558 1559 if (splitPane.isContinuousLayout()) 1560 splitPane.setDividerLocation(location); 1561 else 1562 { 1563 Point p = nonContinuousLayoutDivider.getLocation(); 1564 if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) 1565 p.x = location; 1566 else 1567 p.y = location; 1568 nonContinuousLayoutDivider.setLocation(p); 1569 } 1570 setLastDragLocation(location); 1571 splitPane.repaint(); 1572 } 1573 1574 /** 1575 * This method is called when the dragging is finished. 1576 * 1577 * @param location The location where the drag finished. 1578 */ 1579 protected void finishDraggingTo(int location) 1580 { 1581 if (nonContinuousLayoutDivider != null) 1582 nonContinuousLayoutDivider.setVisible(false); 1583 draggingHW = false; 1584 location = validLocation(location); 1585 splitPane.setDividerLocation(location); 1586 splitPane.setLastDividerLocation(beginDragDividerLocation); 1587 beginDragDividerLocation = -1; 1588 } 1589 1590 /** 1591 * This method returns the width of one of the sides of the divider's border. 1592 * 1593 * @return The width of one side of the divider's border. 1594 * 1595 * @deprecated 1.3 1596 */ 1597 protected int getDividerBorderSize() 1598 { 1599 if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT) 1600 return divider.getBorder().getBorderInsets(divider).left; 1601 else 1602 return divider.getBorder().getBorderInsets(divider).top; 1603 } 1604 1605 /** 1606 * This is a helper method that returns a valid location for the divider 1607 * when dragging. 1608 * 1609 * @param location The location to check. 1610 * 1611 * @return A valid location. 1612 */ 1613 private int validLocation(int location) 1614 { 1615 int min = getMinimumDividerLocation(splitPane); 1616 int max = getMaximumDividerLocation(splitPane); 1617 if (min > 0 && location < min) 1618 return min; 1619 if (max > 0 && location > max) 1620 return max; 1621 return location; 1622 } 1623 }