1 /* 2 * Copyright (c) 1997, 2021, 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 28 package javax.swing.plaf.basic; 29 30 31 32 import java.awt.*; 33 import java.awt.event.*; 34 import javax.swing.*; 35 import javax.swing.event.*; 36 import javax.swing.plaf.*; 37 import javax.swing.border.Border; 38 import java.beans.*; 39 import sun.swing.DefaultLookup; 40 41 42 43 /** 44 * Divider used by BasicSplitPaneUI. Subclassers may wish to override 45 * paint to do something more interesting. 46 * The border effect is drawn in BasicSplitPaneUI, so if you don't like 47 * that border, reset it there. 48 * To conditionally drag from certain areas subclass mousePressed and 49 * call super when you wish the dragging to begin. 50 * <p> 51 * <strong>Warning:</strong> 52 * Serialized objects of this class will not be compatible with 53 * future Swing releases. The current serialization support is 54 * appropriate for short term storage or RMI between applications running 55 * the same version of Swing. As of 1.4, support for long term storage 56 * of all JavaBeans 57 * has been added to the <code>java.beans</code> package. 58 * Please see {@link java.beans.XMLEncoder}. 59 * 60 * @author Scott Violet 61 */ 62 @SuppressWarnings("serial") // Same-version serialization only 63 public class BasicSplitPaneDivider extends Container 64 implements PropertyChangeListener 65 { 66 /** 67 * Width or height of the divider based on orientation 68 * {@code BasicSplitPaneUI} adds two to this. 69 */ 70 protected static final int ONE_TOUCH_SIZE = 6; 71 72 /** 73 * The offset of the divider. 74 */ 75 protected static final int ONE_TOUCH_OFFSET = 2; 76 77 /** 78 * Handles mouse dragging message to do the actual dragging. 79 */ 80 protected DragController dragger; 81 82 /** 83 * UI this instance was created from. 84 */ 85 protected BasicSplitPaneUI splitPaneUI; 86 87 /** 88 * Size of the divider. 89 */ 90 protected int dividerSize = 0; // default - SET TO 0??? 91 92 /** 93 * Divider that is used for noncontinuous layout mode. 94 */ 95 protected Component hiddenDivider; 96 97 /** 98 * JSplitPane the receiver is contained in. 99 */ 100 protected JSplitPane splitPane; 101 102 /** 103 * Handles mouse events from both this class, and the split pane. 104 * Mouse events are handled for the splitpane since you want to be able 105 * to drag when clicking on the border of the divider, which is not 106 * drawn by the divider. 107 */ 108 protected MouseHandler mouseHandler; 109 110 /** 111 * Orientation of the JSplitPane. 112 */ 113 protected int orientation; 114 115 /** 116 * Button for quickly toggling the left component. 117 */ 118 protected JButton leftButton; 119 120 /** 121 * Button for quickly toggling the right component. 122 */ 123 protected JButton rightButton; 124 125 /** Border. */ 126 private Border border; 127 128 /** 129 * Is the mouse over the divider? 130 */ 131 private boolean mouseOver; 132 133 private int oneTouchSize; 134 private int oneTouchOffset; 135 136 /** 137 * If true the one touch buttons are centered on the divider. 138 */ 139 private boolean centerOneTouchButtons; 140 141 142 /** 143 * Creates an instance of {@code BasicSplitPaneDivider}. Registers this 144 * instance for mouse events and mouse dragged events. 145 * 146 * @param ui an instance of {@code BasicSplitPaneUI} 147 */ BasicSplitPaneDivider(BasicSplitPaneUI ui)148 public BasicSplitPaneDivider(BasicSplitPaneUI ui) { 149 oneTouchSize = DefaultLookup.getInt(ui.getSplitPane(), ui, 150 "SplitPane.oneTouchButtonSize", ONE_TOUCH_SIZE); 151 oneTouchOffset = DefaultLookup.getInt(ui.getSplitPane(), ui, 152 "SplitPane.oneTouchButtonOffset", ONE_TOUCH_OFFSET); 153 centerOneTouchButtons = DefaultLookup.getBoolean(ui.getSplitPane(), 154 ui, "SplitPane.centerOneTouchButtons", true); 155 setLayout(new DividerLayout()); 156 setBasicSplitPaneUI(ui); 157 orientation = splitPane.getOrientation(); 158 setCursor((orientation == JSplitPane.HORIZONTAL_SPLIT) ? 159 Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) : 160 Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); 161 setBackground(UIManager.getColor("SplitPane.background")); 162 } 163 revalidateSplitPane()164 private void revalidateSplitPane() { 165 invalidate(); 166 if (splitPane != null) { 167 splitPane.revalidate(); 168 } 169 } 170 171 /** 172 * Sets the {@code SplitPaneUI} that is using the receiver. 173 * 174 * @param newUI the new {@code SplitPaneUI} 175 */ setBasicSplitPaneUI(BasicSplitPaneUI newUI)176 public void setBasicSplitPaneUI(BasicSplitPaneUI newUI) { 177 if (splitPane != null) { 178 splitPane.removePropertyChangeListener(this); 179 if (mouseHandler != null) { 180 splitPane.removeMouseListener(mouseHandler); 181 splitPane.removeMouseMotionListener(mouseHandler); 182 removeMouseListener(mouseHandler); 183 removeMouseMotionListener(mouseHandler); 184 mouseHandler = null; 185 } 186 } 187 splitPaneUI = newUI; 188 if (newUI != null) { 189 splitPane = newUI.getSplitPane(); 190 if (splitPane != null) { 191 if (mouseHandler == null) mouseHandler = new MouseHandler(); 192 splitPane.addMouseListener(mouseHandler); 193 splitPane.addMouseMotionListener(mouseHandler); 194 addMouseListener(mouseHandler); 195 addMouseMotionListener(mouseHandler); 196 splitPane.addPropertyChangeListener(this); 197 if (splitPane.isOneTouchExpandable()) { 198 oneTouchExpandableChanged(); 199 } 200 } 201 } 202 else { 203 splitPane = null; 204 } 205 } 206 207 208 /** 209 * Returns the {@code SplitPaneUI} the receiver is currently in. 210 * 211 * @return the {@code SplitPaneUI} the receiver is currently in 212 */ getBasicSplitPaneUI()213 public BasicSplitPaneUI getBasicSplitPaneUI() { 214 return splitPaneUI; 215 } 216 217 218 /** 219 * Sets the size of the divider to {@code newSize}. That is 220 * the width if the splitpane is {@code HORIZONTAL_SPLIT}, or 221 * the height of {@code VERTICAL_SPLIT}. 222 * 223 * @param newSize a new size 224 */ setDividerSize(int newSize)225 public void setDividerSize(int newSize) { 226 dividerSize = newSize; 227 } 228 229 230 /** 231 * Returns the size of the divider, that is the width if the splitpane 232 * is HORIZONTAL_SPLIT, or the height of VERTICAL_SPLIT. 233 * 234 * @return the size of the divider 235 */ getDividerSize()236 public int getDividerSize() { 237 return dividerSize; 238 } 239 240 241 /** 242 * Sets the border of this component. 243 * 244 * @param border a new border 245 * @since 1.3 246 */ setBorder(Border border)247 public void setBorder(Border border) { 248 Border oldBorder = this.border; 249 250 this.border = border; 251 } 252 253 /** 254 * Returns the border of this component or null if no border is 255 * currently set. 256 * 257 * @return the border object for this component 258 * @see #setBorder 259 * @since 1.3 260 */ getBorder()261 public Border getBorder() { 262 return border; 263 } 264 265 /** 266 * If a border has been set on this component, returns the 267 * border's insets, else calls super.getInsets. 268 * 269 * @return the value of the insets property. 270 * @see #setBorder 271 */ getInsets()272 public Insets getInsets() { 273 Border border = getBorder(); 274 275 if (border != null) { 276 return border.getBorderInsets(this); 277 } 278 return super.getInsets(); 279 } 280 281 /** 282 * Sets whether or not the mouse is currently over the divider. 283 * 284 * @param mouseOver whether or not the mouse is currently over the divider 285 * @since 1.5 286 */ setMouseOver(boolean mouseOver)287 protected void setMouseOver(boolean mouseOver) { 288 this.mouseOver = mouseOver; 289 } 290 291 /** 292 * Returns whether or not the mouse is currently over the divider 293 * 294 * @return whether or not the mouse is currently over the divider 295 * @since 1.5 296 */ isMouseOver()297 public boolean isMouseOver() { 298 return mouseOver; 299 } 300 301 /** 302 * Returns the preferred size of the divider. 303 * @implNote In current implementation, 304 * if the splitpane is HORIZONTAL_SPLIT, the preferred size is obtained from 305 * width of {@code getDividerSize} pixels and height of 1 pixel 306 * If the splitpane is VERTICAL_SPLIT, the preferred size is obtained from 307 * height of {@code getDividerSize} pixels and width of 1 pixel 308 * 309 * @return a {@code Dimension} object containing the preferred size of 310 * {@code BasicSplitPaneDivider} 311 */ getPreferredSize()312 public Dimension getPreferredSize() { 313 // Ideally this would return the size from the layout manager, 314 // but that could result in the layed out size being different from 315 // the dividerSize, which may break developers as well as 316 // BasicSplitPaneUI. 317 if (orientation == JSplitPane.HORIZONTAL_SPLIT) { 318 return new Dimension(getDividerSize(), 1); 319 } 320 return new Dimension(1, getDividerSize()); 321 } 322 323 /** 324 * Returns the minimum size of the divider. 325 * @implNote In current implementation, 326 * if the splitpane is HORIZONTAL_SPLIT, the minimum size is obtained from 327 * width of {@code getDividerSize} pixels and height of 1 pixel 328 * If the splitpane is VERTICAL_SPLIT, the minimum size is obtained from 329 * height of {@code getDividerSize} pixels and width of 1 pixel 330 * 331 * @return a {@code Dimension} object containing the minimum size of 332 * {@code BasicSplitPaneDivider} 333 */ getMinimumSize()334 public Dimension getMinimumSize() { 335 return getPreferredSize(); 336 } 337 338 339 /** 340 * Property change event, presumably from the JSplitPane, will message 341 * updateOrientation if necessary. 342 */ propertyChange(PropertyChangeEvent e)343 public void propertyChange(PropertyChangeEvent e) { 344 if (e.getSource() == splitPane) { 345 if (e.getPropertyName() == JSplitPane.ORIENTATION_PROPERTY) { 346 orientation = splitPane.getOrientation(); 347 setCursor((orientation == JSplitPane.HORIZONTAL_SPLIT) ? 348 Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) : 349 Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); 350 revalidateSplitPane(); 351 } 352 else if (e.getPropertyName() == JSplitPane. 353 ONE_TOUCH_EXPANDABLE_PROPERTY) { 354 oneTouchExpandableChanged(); 355 } 356 } 357 } 358 359 360 /** 361 * Paints the divider. 362 */ paint(Graphics g)363 public void paint(Graphics g) { 364 super.paint(g); 365 366 // Paint the border. 367 Border border = getBorder(); 368 369 if (border != null) { 370 Dimension size = getSize(); 371 372 border.paintBorder(this, g, 0, 0, size.width, size.height); 373 } 374 } 375 376 377 /** 378 * Messaged when the oneTouchExpandable value of the JSplitPane the 379 * receiver is contained in changes. Will create the 380 * <code>leftButton</code> and <code>rightButton</code> if they 381 * are null. invalidates the receiver as well. 382 */ oneTouchExpandableChanged()383 protected void oneTouchExpandableChanged() { 384 if (!DefaultLookup.getBoolean(splitPane, splitPaneUI, 385 "SplitPane.supportsOneTouchButtons", true)) { 386 // Look and feel doesn't want to support one touch buttons, bail. 387 return; 388 } 389 if (splitPane.isOneTouchExpandable() && 390 leftButton == null && 391 rightButton == null) { 392 /* Create the left button and add an action listener to 393 expand/collapse it. */ 394 leftButton = createLeftOneTouchButton(); 395 if (leftButton != null) 396 leftButton.addActionListener(new OneTouchActionHandler(true)); 397 398 399 /* Create the right button and add an action listener to 400 expand/collapse it. */ 401 rightButton = createRightOneTouchButton(); 402 if (rightButton != null) 403 rightButton.addActionListener(new OneTouchActionHandler 404 (false)); 405 406 if (leftButton != null && rightButton != null) { 407 add(leftButton); 408 add(rightButton); 409 } 410 } 411 revalidateSplitPane(); 412 } 413 414 415 /** 416 * Creates and return an instance of {@code JButton} that can be used to 417 * collapse the left component in the split pane. 418 * 419 * @return an instance of {@code JButton} 420 */ createLeftOneTouchButton()421 protected JButton createLeftOneTouchButton() { 422 JButton b = new JButton() { 423 public void setBorder(Border b) { 424 } 425 public void paint(Graphics g) { 426 if (splitPane != null) { 427 int[] xs = new int[3]; 428 int[] ys = new int[3]; 429 int blockSize; 430 431 // Fill the background first ... 432 g.setColor(this.getBackground()); 433 g.fillRect(0, 0, this.getWidth(), 434 this.getHeight()); 435 436 // ... then draw the arrow. 437 g.setColor(Color.black); 438 if (orientation == JSplitPane.VERTICAL_SPLIT) { 439 blockSize = Math.min(getHeight(), oneTouchSize); 440 xs[0] = blockSize; 441 xs[1] = 0; 442 xs[2] = blockSize << 1; 443 ys[0] = 0; 444 ys[1] = ys[2] = blockSize; 445 g.drawPolygon(xs, ys, 3); // Little trick to make the 446 // arrows of equal size 447 } 448 else { 449 blockSize = Math.min(getWidth(), oneTouchSize); 450 xs[0] = xs[2] = blockSize; 451 xs[1] = 0; 452 ys[0] = 0; 453 ys[1] = blockSize; 454 ys[2] = blockSize << 1; 455 } 456 g.fillPolygon(xs, ys, 3); 457 } 458 } 459 // Don't want the button to participate in focus traversable. 460 @SuppressWarnings("deprecation") 461 public boolean isFocusTraversable() { 462 return false; 463 } 464 }; 465 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize)); 466 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 467 b.setFocusPainted(false); 468 b.setBorderPainted(false); 469 b.setRequestFocusEnabled(false); 470 return b; 471 } 472 473 474 /** 475 * Creates and return an instance of {@code JButton} that can be used to 476 * collapse the right component in the split pane. 477 * 478 * @return an instance of {@code JButton} 479 */ createRightOneTouchButton()480 protected JButton createRightOneTouchButton() { 481 JButton b = new JButton() { 482 public void setBorder(Border border) { 483 } 484 public void paint(Graphics g) { 485 if (splitPane != null) { 486 int[] xs = new int[3]; 487 int[] ys = new int[3]; 488 int blockSize; 489 490 // Fill the background first ... 491 g.setColor(this.getBackground()); 492 g.fillRect(0, 0, this.getWidth(), 493 this.getHeight()); 494 495 // ... then draw the arrow. 496 if (orientation == JSplitPane.VERTICAL_SPLIT) { 497 blockSize = Math.min(getHeight(), oneTouchSize); 498 xs[0] = blockSize; 499 xs[1] = blockSize << 1; 500 xs[2] = 0; 501 ys[0] = blockSize; 502 ys[1] = ys[2] = 0; 503 } 504 else { 505 blockSize = Math.min(getWidth(), oneTouchSize); 506 xs[0] = xs[2] = 0; 507 xs[1] = blockSize; 508 ys[0] = 0; 509 ys[1] = blockSize; 510 ys[2] = blockSize << 1; 511 } 512 g.setColor(Color.black); 513 g.fillPolygon(xs, ys, 3); 514 } 515 } 516 // Don't want the button to participate in focus traversable. 517 @SuppressWarnings("deprecation") 518 public boolean isFocusTraversable() { 519 return false; 520 } 521 }; 522 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize)); 523 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 524 b.setFocusPainted(false); 525 b.setBorderPainted(false); 526 b.setRequestFocusEnabled(false); 527 return b; 528 } 529 530 531 /** 532 * Message to prepare for dragging. This messages the BasicSplitPaneUI 533 * with startDragging. 534 */ prepareForDragging()535 protected void prepareForDragging() { 536 splitPaneUI.startDragging(); 537 } 538 539 540 /** 541 * Messages the BasicSplitPaneUI with dragDividerTo that this instance 542 * is contained in. 543 * 544 * @param location a location 545 */ dragDividerTo(int location)546 protected void dragDividerTo(int location) { 547 splitPaneUI.dragDividerTo(location); 548 } 549 550 551 /** 552 * Messages the BasicSplitPaneUI with finishDraggingTo that this instance 553 * is contained in. 554 * 555 * @param location a location 556 */ finishDraggingTo(int location)557 protected void finishDraggingTo(int location) { 558 splitPaneUI.finishDraggingTo(location); 559 } 560 561 562 /** 563 * MouseHandler is responsible for converting mouse events 564 * (released, dragged...) into the appropriate DragController 565 * methods. 566 * 567 */ 568 protected class MouseHandler extends MouseAdapter 569 implements MouseMotionListener 570 { 571 /** 572 * Constructs a {@code MouseHandler}. 573 */ MouseHandler()574 protected MouseHandler() {} 575 576 /** 577 * Starts the dragging session by creating the appropriate instance 578 * of DragController. 579 */ mousePressed(MouseEvent e)580 public void mousePressed(MouseEvent e) { 581 if ((e.getSource() == BasicSplitPaneDivider.this || 582 e.getSource() == splitPane) && 583 dragger == null &&splitPane.isEnabled()) { 584 Component newHiddenDivider = splitPaneUI. 585 getNonContinuousLayoutDivider(); 586 587 if (hiddenDivider != newHiddenDivider) { 588 if (hiddenDivider != null) { 589 hiddenDivider.removeMouseListener(this); 590 hiddenDivider.removeMouseMotionListener(this); 591 } 592 hiddenDivider = newHiddenDivider; 593 if (hiddenDivider != null) { 594 hiddenDivider.addMouseMotionListener(this); 595 hiddenDivider.addMouseListener(this); 596 } 597 } 598 if (splitPane.getLeftComponent() != null && 599 splitPane.getRightComponent() != null) { 600 if (orientation == JSplitPane.HORIZONTAL_SPLIT) { 601 dragger = new DragController(e); 602 } 603 else { 604 dragger = new VerticalDragController(e); 605 } 606 if (!dragger.isValid()) { 607 dragger = null; 608 } 609 else { 610 prepareForDragging(); 611 dragger.continueDrag(e); 612 } 613 } 614 e.consume(); 615 } 616 } 617 618 619 /** 620 * If dragger is not null it is messaged with completeDrag. 621 */ mouseReleased(MouseEvent e)622 public void mouseReleased(MouseEvent e) { 623 if (dragger != null) { 624 if (e.getSource() == splitPane) { 625 dragger.completeDrag(e.getX(), e.getY()); 626 } 627 else if (e.getSource() == BasicSplitPaneDivider.this) { 628 Point ourLoc = getLocation(); 629 630 dragger.completeDrag(e.getX() + ourLoc.x, 631 e.getY() + ourLoc.y); 632 } 633 else if (e.getSource() == hiddenDivider) { 634 Point hDividerLoc = hiddenDivider.getLocation(); 635 int ourX = e.getX() + hDividerLoc.x; 636 int ourY = e.getY() + hDividerLoc.y; 637 638 dragger.completeDrag(ourX, ourY); 639 } 640 dragger = null; 641 e.consume(); 642 } 643 } 644 645 646 // 647 // MouseMotionListener 648 // 649 650 /** 651 * If dragger is not null it is messaged with continueDrag. 652 */ mouseDragged(MouseEvent e)653 public void mouseDragged(MouseEvent e) { 654 if (dragger != null) { 655 if (e.getSource() == splitPane) { 656 dragger.continueDrag(e.getX(), e.getY()); 657 } 658 else if (e.getSource() == BasicSplitPaneDivider.this) { 659 Point ourLoc = getLocation(); 660 661 dragger.continueDrag(e.getX() + ourLoc.x, 662 e.getY() + ourLoc.y); 663 } 664 else if (e.getSource() == hiddenDivider) { 665 Point hDividerLoc = hiddenDivider.getLocation(); 666 int ourX = e.getX() + hDividerLoc.x; 667 int ourY = e.getY() + hDividerLoc.y; 668 669 dragger.continueDrag(ourX, ourY); 670 } 671 e.consume(); 672 } 673 } 674 675 676 /** 677 * Resets the cursor based on the orientation. 678 */ mouseMoved(MouseEvent e)679 public void mouseMoved(MouseEvent e) { 680 } 681 682 /** 683 * Invoked when the mouse enters a component. 684 * 685 * @param e MouseEvent describing the details of the enter event. 686 * @since 1.5 687 */ mouseEntered(MouseEvent e)688 public void mouseEntered(MouseEvent e) { 689 if (e.getSource() == BasicSplitPaneDivider.this) { 690 setMouseOver(true); 691 } 692 } 693 694 /** 695 * Invoked when the mouse exits a component. 696 * 697 * @param e MouseEvent describing the details of the exit event. 698 * @since 1.5 699 */ mouseExited(MouseEvent e)700 public void mouseExited(MouseEvent e) { 701 if (e.getSource() == BasicSplitPaneDivider.this) { 702 setMouseOver(false); 703 } 704 } 705 } 706 707 708 /** 709 * Handles the events during a dragging session for a 710 * HORIZONTAL_SPLIT oriented split pane. This continually 711 * messages <code>dragDividerTo</code> and then when done messages 712 * <code>finishDraggingTo</code>. When an instance is created it should be 713 * messaged with <code>isValid</code> to insure that dragging can happen 714 * (dragging won't be allowed if the two views can not be resized). 715 * <p> 716 * <strong>Warning:</strong> 717 * Serialized objects of this class will not be compatible with 718 * future Swing releases. The current serialization support is 719 * appropriate for short term storage or RMI between applications running 720 * the same version of Swing. As of 1.4, support for long term storage 721 * of all JavaBeans 722 * has been added to the <code>java.beans</code> package. 723 * Please see {@link java.beans.XMLEncoder}. 724 */ 725 @SuppressWarnings("serial") // Same-version serialization only 726 protected class DragController 727 { 728 /** 729 * Initial location of the divider. 730 */ 731 int initialX; 732 733 /** 734 * Maximum and minimum positions to drag to. 735 */ 736 int maxX, minX; 737 738 /** 739 * Initial location the mouse down happened at. 740 */ 741 int offset; 742 743 /** 744 * Constructs a new instance of {@code DragController}. 745 * 746 * @param e a mouse event 747 */ DragController(MouseEvent e)748 protected DragController(MouseEvent e) { 749 JSplitPane splitPane = splitPaneUI.getSplitPane(); 750 Component leftC = splitPane.getLeftComponent(); 751 Component rightC = splitPane.getRightComponent(); 752 753 initialX = getLocation().x; 754 if (e.getSource() == BasicSplitPaneDivider.this) { 755 offset = e.getX(); 756 } 757 else { // splitPane 758 offset = e.getX() - initialX; 759 } 760 if (leftC == null || rightC == null || offset < -1 || 761 offset >= getSize().width) { 762 // Don't allow dragging. 763 maxX = -1; 764 } 765 else { 766 Insets insets = splitPane.getInsets(); 767 768 if (leftC.isVisible()) { 769 minX = leftC.getMinimumSize().width; 770 if (insets != null) { 771 minX += insets.left; 772 } 773 } 774 else { 775 minX = 0; 776 } 777 if (rightC.isVisible()) { 778 int right = (insets != null) ? insets.right : 0; 779 maxX = Math.max(0, splitPane.getSize().width - 780 (getSize().width + right) - 781 rightC.getMinimumSize().width); 782 } 783 else { 784 int right = (insets != null) ? insets.right : 0; 785 maxX = Math.max(0, splitPane.getSize().width - 786 (getSize().width + right)); 787 } 788 if (maxX < minX) minX = maxX = 0; 789 } 790 } 791 792 793 /** 794 * Returns {@code true} if the dragging session is valid. 795 * 796 * @return {@code true} if the dragging session is valid 797 */ isValid()798 protected boolean isValid() { 799 return (maxX > 0); 800 } 801 802 803 /** 804 * Returns the new position to put the divider at based on 805 * the passed in MouseEvent. 806 * 807 * @param e a mouse event 808 * @return the new position 809 */ positionForMouseEvent(MouseEvent e)810 protected int positionForMouseEvent(MouseEvent e) { 811 int newX = (e.getSource() == BasicSplitPaneDivider.this) ? 812 (e.getX() + getLocation().x) : e.getX(); 813 814 newX = Math.min(maxX, Math.max(minX, newX - offset)); 815 return newX; 816 } 817 818 819 /** 820 * Returns the x argument, since this is used for horizontal 821 * splits. 822 * 823 * @param x an X coordinate 824 * @param y an Y coordinate 825 * @return the X argument 826 */ getNeededLocation(int x, int y)827 protected int getNeededLocation(int x, int y) { 828 int newX; 829 830 newX = Math.min(maxX, Math.max(minX, x - offset)); 831 return newX; 832 } 833 834 /** 835 * Messages dragDividerTo with the new location for the mouse 836 * event. 837 * 838 * @param newX an X coordinate 839 * @param newY an Y coordinate 840 */ continueDrag(int newX, int newY)841 protected void continueDrag(int newX, int newY) { 842 dragDividerTo(getNeededLocation(newX, newY)); 843 } 844 845 846 /** 847 * Messages dragDividerTo with the new location for the mouse 848 * event. 849 * 850 * @param e a mouse event 851 */ continueDrag(MouseEvent e)852 protected void continueDrag(MouseEvent e) { 853 dragDividerTo(positionForMouseEvent(e)); 854 } 855 856 /** 857 * Messages finishDraggingTo with the new location for the mouse 858 * event. 859 * 860 * @param x an X coordinate 861 * @param y an Y coordinate 862 */ completeDrag(int x, int y)863 protected void completeDrag(int x, int y) { 864 finishDraggingTo(getNeededLocation(x, y)); 865 } 866 867 868 /** 869 * Messages finishDraggingTo with the new location for the mouse 870 * event. 871 * 872 * @param e a mouse event 873 */ completeDrag(MouseEvent e)874 protected void completeDrag(MouseEvent e) { 875 finishDraggingTo(positionForMouseEvent(e)); 876 } 877 } // End of BasicSplitPaneDivider.DragController 878 879 880 /** 881 * Handles the events during a dragging session for a 882 * VERTICAL_SPLIT oriented split pane. This continually 883 * messages <code>dragDividerTo</code> and then when done messages 884 * <code>finishDraggingTo</code>. When an instance is created it should be 885 * messaged with <code>isValid</code> to insure that dragging can happen 886 * (dragging won't be allowed if the two views can not be resized). 887 */ 888 protected class VerticalDragController extends DragController 889 { 890 /* DragControllers ivars are now in terms of y, not x. */ 891 /** 892 * Constructs a new instance of {@code VerticalDragController}. 893 * 894 * @param e a mouse event 895 */ VerticalDragController(MouseEvent e)896 protected VerticalDragController(MouseEvent e) { 897 super(e); 898 JSplitPane splitPane = splitPaneUI.getSplitPane(); 899 Component leftC = splitPane.getLeftComponent(); 900 Component rightC = splitPane.getRightComponent(); 901 902 initialX = getLocation().y; 903 if (e.getSource() == BasicSplitPaneDivider.this) { 904 offset = e.getY(); 905 } 906 else { 907 offset = e.getY() - initialX; 908 } 909 if (leftC == null || rightC == null || offset < -1 || 910 offset > getSize().height) { 911 // Don't allow dragging. 912 maxX = -1; 913 } 914 else { 915 Insets insets = splitPane.getInsets(); 916 917 if (leftC.isVisible()) { 918 minX = leftC.getMinimumSize().height; 919 if (insets != null) { 920 minX += insets.top; 921 } 922 } 923 else { 924 minX = 0; 925 } 926 if (rightC.isVisible()) { 927 int bottom = (insets != null) ? insets.bottom : 0; 928 929 maxX = Math.max(0, splitPane.getSize().height - 930 (getSize().height + bottom) - 931 rightC.getMinimumSize().height); 932 } 933 else { 934 int bottom = (insets != null) ? insets.bottom : 0; 935 936 maxX = Math.max(0, splitPane.getSize().height - 937 (getSize().height + bottom)); 938 } 939 if (maxX < minX) minX = maxX = 0; 940 } 941 } 942 943 944 /** 945 * Returns the y argument, since this is used for vertical 946 * splits. 947 */ getNeededLocation(int x, int y)948 protected int getNeededLocation(int x, int y) { 949 int newY; 950 951 newY = Math.min(maxX, Math.max(minX, y - offset)); 952 return newY; 953 } 954 955 956 /** 957 * Returns the new position to put the divider at based on 958 * the passed in MouseEvent. 959 */ positionForMouseEvent(MouseEvent e)960 protected int positionForMouseEvent(MouseEvent e) { 961 int newY = (e.getSource() == BasicSplitPaneDivider.this) ? 962 (e.getY() + getLocation().y) : e.getY(); 963 964 965 newY = Math.min(maxX, Math.max(minX, newY - offset)); 966 return newY; 967 } 968 } // End of BasicSplitPaneDividier.VerticalDragController 969 970 971 /** 972 * Used to layout a <code>BasicSplitPaneDivider</code>. 973 * Layout for the divider 974 * involves appropriately moving the left/right buttons around. 975 * 976 */ 977 protected class DividerLayout implements LayoutManager 978 { 979 /** 980 * Constructs a {@code DividerLayout}. 981 */ DividerLayout()982 protected DividerLayout() {} 983 layoutContainer(Container c)984 public void layoutContainer(Container c) { 985 if (leftButton != null && rightButton != null && 986 c == BasicSplitPaneDivider.this) { 987 if (splitPane.isOneTouchExpandable()) { 988 Insets insets = getInsets(); 989 990 if (orientation == JSplitPane.VERTICAL_SPLIT) { 991 int extraX = (insets != null) ? insets.left : 0; 992 int blockSize = getHeight(); 993 994 if (insets != null) { 995 blockSize -= (insets.top + insets.bottom); 996 blockSize = Math.max(blockSize, 0); 997 } 998 blockSize = Math.min(blockSize, oneTouchSize); 999 1000 int y = (c.getSize().height - blockSize) / 2; 1001 1002 if (!centerOneTouchButtons) { 1003 y = (insets != null) ? insets.top : 0; 1004 extraX = 0; 1005 } 1006 leftButton.setBounds(extraX + oneTouchOffset, y, 1007 blockSize * 2, blockSize); 1008 rightButton.setBounds(extraX + oneTouchOffset + 1009 oneTouchSize * 2, y, 1010 blockSize * 2, blockSize); 1011 } 1012 else { 1013 int extraY = (insets != null) ? insets.top : 0; 1014 int blockSize = getWidth(); 1015 1016 if (insets != null) { 1017 blockSize -= (insets.left + insets.right); 1018 blockSize = Math.max(blockSize, 0); 1019 } 1020 blockSize = Math.min(blockSize, oneTouchSize); 1021 1022 int x = (c.getSize().width - blockSize) / 2; 1023 1024 if (!centerOneTouchButtons) { 1025 x = (insets != null) ? insets.left : 0; 1026 extraY = 0; 1027 } 1028 1029 leftButton.setBounds(x, extraY + oneTouchOffset, 1030 blockSize, blockSize * 2); 1031 rightButton.setBounds(x, extraY + oneTouchOffset + 1032 oneTouchSize * 2, blockSize, 1033 blockSize * 2); 1034 } 1035 } 1036 else { 1037 leftButton.setBounds(-5, -5, 1, 1); 1038 rightButton.setBounds(-5, -5, 1, 1); 1039 } 1040 } 1041 } 1042 1043 minimumLayoutSize(Container c)1044 public Dimension minimumLayoutSize(Container c) { 1045 // NOTE: This isn't really used, refer to 1046 // BasicSplitPaneDivider.getPreferredSize for the reason. 1047 // I leave it in hopes of having this used at some point. 1048 if (c != BasicSplitPaneDivider.this || splitPane == null) { 1049 return new Dimension(0,0); 1050 } 1051 Dimension buttonMinSize = null; 1052 1053 if (splitPane.isOneTouchExpandable() && leftButton != null) { 1054 buttonMinSize = leftButton.getMinimumSize(); 1055 } 1056 1057 Insets insets = getInsets(); 1058 int width = getDividerSize(); 1059 int height = width; 1060 1061 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1062 if (buttonMinSize != null) { 1063 int size = buttonMinSize.height; 1064 if (insets != null) { 1065 size += insets.top + insets.bottom; 1066 } 1067 height = Math.max(height, size); 1068 } 1069 width = 1; 1070 } 1071 else { 1072 if (buttonMinSize != null) { 1073 int size = buttonMinSize.width; 1074 if (insets != null) { 1075 size += insets.left + insets.right; 1076 } 1077 width = Math.max(width, size); 1078 } 1079 height = 1; 1080 } 1081 return new Dimension(width, height); 1082 } 1083 1084 preferredLayoutSize(Container c)1085 public Dimension preferredLayoutSize(Container c) { 1086 return minimumLayoutSize(c); 1087 } 1088 1089 removeLayoutComponent(Component c)1090 public void removeLayoutComponent(Component c) {} 1091 addLayoutComponent(String string, Component c)1092 public void addLayoutComponent(String string, Component c) {} 1093 } // End of class BasicSplitPaneDivider.DividerLayout 1094 1095 1096 /** 1097 * Listeners installed on the one touch expandable buttons. 1098 */ 1099 private class OneTouchActionHandler implements ActionListener { 1100 /** True indicates the resize should go the minimum (top or left) 1101 * vs false which indicates the resize should go to the maximum. 1102 */ 1103 private boolean toMinimum; 1104 OneTouchActionHandler(boolean toMinimum)1105 OneTouchActionHandler(boolean toMinimum) { 1106 this.toMinimum = toMinimum; 1107 } 1108 actionPerformed(ActionEvent e)1109 public void actionPerformed(ActionEvent e) { 1110 Insets insets = splitPane.getInsets(); 1111 int lastLoc = splitPane.getLastDividerLocation(); 1112 int currentLoc = splitPaneUI.getDividerLocation(splitPane); 1113 int newLoc; 1114 1115 // We use the location from the UI directly, as the location the 1116 // JSplitPane itself maintains is not necessarly correct. 1117 if (toMinimum) { 1118 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1119 if (currentLoc >= (splitPane.getHeight() - 1120 insets.bottom - getHeight())) { 1121 int maxLoc = splitPane.getMaximumDividerLocation(); 1122 newLoc = Math.min(lastLoc, maxLoc); 1123 splitPaneUI.setKeepHidden(false); 1124 } 1125 else { 1126 newLoc = insets.top; 1127 splitPaneUI.setKeepHidden(true); 1128 } 1129 } 1130 else { 1131 if (currentLoc >= (splitPane.getWidth() - 1132 insets.right - getWidth())) { 1133 int maxLoc = splitPane.getMaximumDividerLocation(); 1134 newLoc = Math.min(lastLoc, maxLoc); 1135 splitPaneUI.setKeepHidden(false); 1136 } 1137 else { 1138 newLoc = insets.left; 1139 splitPaneUI.setKeepHidden(true); 1140 } 1141 } 1142 } 1143 else { 1144 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1145 if (currentLoc == insets.top) { 1146 int maxLoc = splitPane.getMaximumDividerLocation(); 1147 newLoc = Math.min(lastLoc, maxLoc); 1148 splitPaneUI.setKeepHidden(false); 1149 } 1150 else { 1151 newLoc = splitPane.getHeight() - getHeight() - 1152 insets.top; 1153 splitPaneUI.setKeepHidden(true); 1154 } 1155 } 1156 else { 1157 if (currentLoc == insets.left) { 1158 int maxLoc = splitPane.getMaximumDividerLocation(); 1159 newLoc = Math.min(lastLoc, maxLoc); 1160 splitPaneUI.setKeepHidden(false); 1161 } 1162 else { 1163 newLoc = splitPane.getWidth() - getWidth() - 1164 insets.left; 1165 splitPaneUI.setKeepHidden(true); 1166 } 1167 } 1168 } 1169 if (currentLoc != newLoc) { 1170 splitPane.setDividerLocation(newLoc); 1171 // We do this in case the dividers notion of the location 1172 // differs from the real location. 1173 splitPane.setLastDividerLocation(currentLoc); 1174 } 1175 } 1176 } // End of class BasicSplitPaneDivider.LeftActionListener 1177 } 1178