1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 297 public boolean isMouseOver() { 298 return mouseOver; 299 } 300 301 /** 302 * Returns dividerSize x dividerSize 303 */ 304 public Dimension getPreferredSize() { 305 // Ideally this would return the size from the layout manager, 306 // but that could result in the layed out size being different from 307 // the dividerSize, which may break developers as well as 308 // BasicSplitPaneUI. 309 if (orientation == JSplitPane.HORIZONTAL_SPLIT) { 310 return new Dimension(getDividerSize(), 1); 311 } 312 return new Dimension(1, getDividerSize()); 313 } 314 315 /** 316 * Returns dividerSize x dividerSize 317 */ 318 public Dimension getMinimumSize() { 319 return getPreferredSize(); 320 } 321 322 323 /** 324 * Property change event, presumably from the JSplitPane, will message 325 * updateOrientation if necessary. 326 */ 327 public void propertyChange(PropertyChangeEvent e) { 328 if (e.getSource() == splitPane) { 329 if (e.getPropertyName() == JSplitPane.ORIENTATION_PROPERTY) { 330 orientation = splitPane.getOrientation(); 331 setCursor((orientation == JSplitPane.HORIZONTAL_SPLIT) ? 332 Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) : 333 Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); 334 revalidateSplitPane(); 335 } 336 else if (e.getPropertyName() == JSplitPane. 337 ONE_TOUCH_EXPANDABLE_PROPERTY) { 338 oneTouchExpandableChanged(); 339 } 340 } 341 } 342 343 344 /** 345 * Paints the divider. 346 */ 347 public void paint(Graphics g) { 348 super.paint(g); 349 350 // Paint the border. 351 Border border = getBorder(); 352 353 if (border != null) { 354 Dimension size = getSize(); 355 356 border.paintBorder(this, g, 0, 0, size.width, size.height); 357 } 358 } 359 360 361 /** 362 * Messaged when the oneTouchExpandable value of the JSplitPane the 363 * receiver is contained in changes. Will create the 364 * <code>leftButton</code> and <code>rightButton</code> if they 365 * are null. invalidates the receiver as well. 366 */ 367 protected void oneTouchExpandableChanged() { 368 if (!DefaultLookup.getBoolean(splitPane, splitPaneUI, 369 "SplitPane.supportsOneTouchButtons", true)) { 370 // Look and feel doesn't want to support one touch buttons, bail. 371 return; 372 } 373 if (splitPane.isOneTouchExpandable() && 374 leftButton == null && 375 rightButton == null) { 376 /* Create the left button and add an action listener to 377 expand/collapse it. */ 378 leftButton = createLeftOneTouchButton(); 379 if (leftButton != null) 380 leftButton.addActionListener(new OneTouchActionHandler(true)); 381 382 383 /* Create the right button and add an action listener to 384 expand/collapse it. */ 385 rightButton = createRightOneTouchButton(); 386 if (rightButton != null) 387 rightButton.addActionListener(new OneTouchActionHandler 388 (false)); 389 390 if (leftButton != null && rightButton != null) { 391 add(leftButton); 392 add(rightButton); 393 } 394 } 395 revalidateSplitPane(); 396 } 397 398 399 /** 400 * Creates and return an instance of {@code JButton} that can be used to 401 * collapse the left component in the split pane. 402 * 403 * @return an instance of {@code JButton} 404 */ 405 protected JButton createLeftOneTouchButton() { 406 JButton b = new JButton() { 407 public void setBorder(Border b) { 408 } 409 public void paint(Graphics g) { 410 if (splitPane != null) { 411 int[] xs = new int[3]; 412 int[] ys = new int[3]; 413 int blockSize; 414 415 // Fill the background first ... 416 g.setColor(this.getBackground()); 417 g.fillRect(0, 0, this.getWidth(), 418 this.getHeight()); 419 420 // ... then draw the arrow. 421 g.setColor(Color.black); 422 if (orientation == JSplitPane.VERTICAL_SPLIT) { 423 blockSize = Math.min(getHeight(), oneTouchSize); 424 xs[0] = blockSize; 425 xs[1] = 0; 426 xs[2] = blockSize << 1; 427 ys[0] = 0; 428 ys[1] = ys[2] = blockSize; 429 g.drawPolygon(xs, ys, 3); // Little trick to make the 430 // arrows of equal size 431 } 432 else { 433 blockSize = Math.min(getWidth(), oneTouchSize); 434 xs[0] = xs[2] = blockSize; 435 xs[1] = 0; 436 ys[0] = 0; 437 ys[1] = blockSize; 438 ys[2] = blockSize << 1; 439 } 440 g.fillPolygon(xs, ys, 3); 441 } 442 } 443 // Don't want the button to participate in focus traversable. 444 @SuppressWarnings("deprecation") 445 public boolean isFocusTraversable() { 446 return false; 447 } 448 }; 449 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize)); 450 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 451 b.setFocusPainted(false); 452 b.setBorderPainted(false); 453 b.setRequestFocusEnabled(false); 454 return b; 455 } 456 457 458 /** 459 * Creates and return an instance of {@code JButton} that can be used to 460 * collapse the right component in the split pane. 461 * 462 * @return an instance of {@code JButton} 463 */ 464 protected JButton createRightOneTouchButton() { 465 JButton b = new JButton() { 466 public void setBorder(Border border) { 467 } 468 public void paint(Graphics g) { 469 if (splitPane != null) { 470 int[] xs = new int[3]; 471 int[] ys = new int[3]; 472 int blockSize; 473 474 // Fill the background first ... 475 g.setColor(this.getBackground()); 476 g.fillRect(0, 0, this.getWidth(), 477 this.getHeight()); 478 479 // ... then draw the arrow. 480 if (orientation == JSplitPane.VERTICAL_SPLIT) { 481 blockSize = Math.min(getHeight(), oneTouchSize); 482 xs[0] = blockSize; 483 xs[1] = blockSize << 1; 484 xs[2] = 0; 485 ys[0] = blockSize; 486 ys[1] = ys[2] = 0; 487 } 488 else { 489 blockSize = Math.min(getWidth(), oneTouchSize); 490 xs[0] = xs[2] = 0; 491 xs[1] = blockSize; 492 ys[0] = 0; 493 ys[1] = blockSize; 494 ys[2] = blockSize << 1; 495 } 496 g.setColor(Color.black); 497 g.fillPolygon(xs, ys, 3); 498 } 499 } 500 // Don't want the button to participate in focus traversable. 501 @SuppressWarnings("deprecation") 502 public boolean isFocusTraversable() { 503 return false; 504 } 505 }; 506 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize)); 507 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 508 b.setFocusPainted(false); 509 b.setBorderPainted(false); 510 b.setRequestFocusEnabled(false); 511 return b; 512 } 513 514 515 /** 516 * Message to prepare for dragging. This messages the BasicSplitPaneUI 517 * with startDragging. 518 */ 519 protected void prepareForDragging() { 520 splitPaneUI.startDragging(); 521 } 522 523 524 /** 525 * Messages the BasicSplitPaneUI with dragDividerTo that this instance 526 * is contained in. 527 * 528 * @param location a location 529 */ 530 protected void dragDividerTo(int location) { 531 splitPaneUI.dragDividerTo(location); 532 } 533 534 535 /** 536 * Messages the BasicSplitPaneUI with finishDraggingTo that this instance 537 * is contained in. 538 * 539 * @param location a location 540 */ 541 protected void finishDraggingTo(int location) { 542 splitPaneUI.finishDraggingTo(location); 543 } 544 545 546 /** 547 * MouseHandler is responsible for converting mouse events 548 * (released, dragged...) into the appropriate DragController 549 * methods. 550 * 551 */ 552 protected class MouseHandler extends MouseAdapter 553 implements MouseMotionListener 554 { 555 /** 556 * Starts the dragging session by creating the appropriate instance 557 * of DragController. 558 */ 559 public void mousePressed(MouseEvent e) { 560 if ((e.getSource() == BasicSplitPaneDivider.this || 561 e.getSource() == splitPane) && 562 dragger == null &&splitPane.isEnabled()) { 563 Component newHiddenDivider = splitPaneUI. 564 getNonContinuousLayoutDivider(); 565 566 if (hiddenDivider != newHiddenDivider) { 567 if (hiddenDivider != null) { 568 hiddenDivider.removeMouseListener(this); 569 hiddenDivider.removeMouseMotionListener(this); 570 } 571 hiddenDivider = newHiddenDivider; 572 if (hiddenDivider != null) { 573 hiddenDivider.addMouseMotionListener(this); 574 hiddenDivider.addMouseListener(this); 575 } 576 } 577 if (splitPane.getLeftComponent() != null && 578 splitPane.getRightComponent() != null) { 579 if (orientation == JSplitPane.HORIZONTAL_SPLIT) { 580 dragger = new DragController(e); 581 } 582 else { 583 dragger = new VerticalDragController(e); 584 } 585 if (!dragger.isValid()) { 586 dragger = null; 587 } 588 else { 589 prepareForDragging(); 590 dragger.continueDrag(e); 591 } 592 } 593 e.consume(); 594 } 595 } 596 597 598 /** 599 * If dragger is not null it is messaged with completeDrag. 600 */ 601 public void mouseReleased(MouseEvent e) { 602 if (dragger != null) { 603 if (e.getSource() == splitPane) { 604 dragger.completeDrag(e.getX(), e.getY()); 605 } 606 else if (e.getSource() == BasicSplitPaneDivider.this) { 607 Point ourLoc = getLocation(); 608 609 dragger.completeDrag(e.getX() + ourLoc.x, 610 e.getY() + ourLoc.y); 611 } 612 else if (e.getSource() == hiddenDivider) { 613 Point hDividerLoc = hiddenDivider.getLocation(); 614 int ourX = e.getX() + hDividerLoc.x; 615 int ourY = e.getY() + hDividerLoc.y; 616 617 dragger.completeDrag(ourX, ourY); 618 } 619 dragger = null; 620 e.consume(); 621 } 622 } 623 624 625 // 626 // MouseMotionListener 627 // 628 629 /** 630 * If dragger is not null it is messaged with continueDrag. 631 */ 632 public void mouseDragged(MouseEvent e) { 633 if (dragger != null) { 634 if (e.getSource() == splitPane) { 635 dragger.continueDrag(e.getX(), e.getY()); 636 } 637 else if (e.getSource() == BasicSplitPaneDivider.this) { 638 Point ourLoc = getLocation(); 639 640 dragger.continueDrag(e.getX() + ourLoc.x, 641 e.getY() + ourLoc.y); 642 } 643 else if (e.getSource() == hiddenDivider) { 644 Point hDividerLoc = hiddenDivider.getLocation(); 645 int ourX = e.getX() + hDividerLoc.x; 646 int ourY = e.getY() + hDividerLoc.y; 647 648 dragger.continueDrag(ourX, ourY); 649 } 650 e.consume(); 651 } 652 } 653 654 655 /** 656 * Resets the cursor based on the orientation. 657 */ 658 public void mouseMoved(MouseEvent e) { 659 } 660 661 /** 662 * Invoked when the mouse enters a component. 663 * 664 * @param e MouseEvent describing the details of the enter event. 665 * @since 1.5 666 */ 667 public void mouseEntered(MouseEvent e) { 668 if (e.getSource() == BasicSplitPaneDivider.this) { 669 setMouseOver(true); 670 } 671 } 672 673 /** 674 * Invoked when the mouse exits a component. 675 * 676 * @param e MouseEvent describing the details of the exit event. 677 * @since 1.5 678 */ 679 public void mouseExited(MouseEvent e) { 680 if (e.getSource() == BasicSplitPaneDivider.this) { 681 setMouseOver(false); 682 } 683 } 684 } 685 686 687 /** 688 * Handles the events during a dragging session for a 689 * HORIZONTAL_SPLIT oriented split pane. This continually 690 * messages <code>dragDividerTo</code> and then when done messages 691 * <code>finishDraggingTo</code>. When an instance is created it should be 692 * messaged with <code>isValid</code> to insure that dragging can happen 693 * (dragging won't be allowed if the two views can not be resized). 694 * <p> 695 * <strong>Warning:</strong> 696 * Serialized objects of this class will not be compatible with 697 * future Swing releases. The current serialization support is 698 * appropriate for short term storage or RMI between applications running 699 * the same version of Swing. As of 1.4, support for long term storage 700 * of all JavaBeans 701 * has been added to the <code>java.beans</code> package. 702 * Please see {@link java.beans.XMLEncoder}. 703 */ 704 @SuppressWarnings("serial") // Same-version serialization only 705 protected class DragController 706 { 707 /** 708 * Initial location of the divider. 709 */ 710 int initialX; 711 712 /** 713 * Maximum and minimum positions to drag to. 714 */ 715 int maxX, minX; 716 717 /** 718 * Initial location the mouse down happened at. 719 */ 720 int offset; 721 722 /** 723 * Constructs a new instance of {@code DragController}. 724 * 725 * @param e a mouse event 726 */ 727 protected DragController(MouseEvent e) { 728 JSplitPane splitPane = splitPaneUI.getSplitPane(); 729 Component leftC = splitPane.getLeftComponent(); 730 Component rightC = splitPane.getRightComponent(); 731 732 initialX = getLocation().x; 733 if (e.getSource() == BasicSplitPaneDivider.this) { 734 offset = e.getX(); 735 } 736 else { // splitPane 737 offset = e.getX() - initialX; 738 } 739 if (leftC == null || rightC == null || offset < -1 || 740 offset >= getSize().width) { 741 // Don't allow dragging. 742 maxX = -1; 743 } 744 else { 745 Insets insets = splitPane.getInsets(); 746 747 if (leftC.isVisible()) { 748 minX = leftC.getMinimumSize().width; 749 if (insets != null) { 750 minX += insets.left; 751 } 752 } 753 else { 754 minX = 0; 755 } 756 if (rightC.isVisible()) { 757 int right = (insets != null) ? insets.right : 0; 758 maxX = Math.max(0, splitPane.getSize().width - 759 (getSize().width + right) - 760 rightC.getMinimumSize().width); 761 } 762 else { 763 int right = (insets != null) ? insets.right : 0; 764 maxX = Math.max(0, splitPane.getSize().width - 765 (getSize().width + right)); 766 } 767 if (maxX < minX) minX = maxX = 0; 768 } 769 } 770 771 772 /** 773 * Returns {@code true} if the dragging session is valid. 774 * 775 * @return {@code true} if the dragging session is valid 776 */ 777 protected boolean isValid() { 778 return (maxX > 0); 779 } 780 781 782 /** 783 * Returns the new position to put the divider at based on 784 * the passed in MouseEvent. 785 * 786 * @param e a mouse event 787 * @return the new position 788 */ 789 protected int positionForMouseEvent(MouseEvent e) { 790 int newX = (e.getSource() == BasicSplitPaneDivider.this) ? 791 (e.getX() + getLocation().x) : e.getX(); 792 793 newX = Math.min(maxX, Math.max(minX, newX - offset)); 794 return newX; 795 } 796 797 798 /** 799 * Returns the x argument, since this is used for horizontal 800 * splits. 801 * 802 * @param x an X coordinate 803 * @param y an Y coordinate 804 * @return the X argument 805 */ 806 protected int getNeededLocation(int x, int y) { 807 int newX; 808 809 newX = Math.min(maxX, Math.max(minX, x - offset)); 810 return newX; 811 } 812 813 /** 814 * Messages dragDividerTo with the new location for the mouse 815 * event. 816 * 817 * @param newX an X coordinate 818 * @param newY an Y coordinate 819 */ 820 protected void continueDrag(int newX, int newY) { 821 dragDividerTo(getNeededLocation(newX, newY)); 822 } 823 824 825 /** 826 * Messages dragDividerTo with the new location for the mouse 827 * event. 828 * 829 * @param e a mouse event 830 */ 831 protected void continueDrag(MouseEvent e) { 832 dragDividerTo(positionForMouseEvent(e)); 833 } 834 835 /** 836 * Messages finishDraggingTo with the new location for the mouse 837 * event. 838 * 839 * @param x an X coordinate 840 * @param y an Y coordinate 841 */ 842 protected void completeDrag(int x, int y) { 843 finishDraggingTo(getNeededLocation(x, y)); 844 } 845 846 847 /** 848 * Messages finishDraggingTo with the new location for the mouse 849 * event. 850 * 851 * @param e a mouse event 852 */ 853 protected void completeDrag(MouseEvent e) { 854 finishDraggingTo(positionForMouseEvent(e)); 855 } 856 } // End of BasicSplitPaneDivider.DragController 857 858 859 /** 860 * Handles the events during a dragging session for a 861 * VERTICAL_SPLIT oriented split pane. This continually 862 * messages <code>dragDividerTo</code> and then when done messages 863 * <code>finishDraggingTo</code>. When an instance is created it should be 864 * messaged with <code>isValid</code> to insure that dragging can happen 865 * (dragging won't be allowed if the two views can not be resized). 866 */ 867 protected class VerticalDragController extends DragController 868 { 869 /* DragControllers ivars are now in terms of y, not x. */ 870 /** 871 * Constructs a new instance of {@code VerticalDragController}. 872 * 873 * @param e a mouse event 874 */ 875 protected VerticalDragController(MouseEvent e) { 876 super(e); 877 JSplitPane splitPane = splitPaneUI.getSplitPane(); 878 Component leftC = splitPane.getLeftComponent(); 879 Component rightC = splitPane.getRightComponent(); 880 881 initialX = getLocation().y; 882 if (e.getSource() == BasicSplitPaneDivider.this) { 883 offset = e.getY(); 884 } 885 else { 886 offset = e.getY() - initialX; 887 } 888 if (leftC == null || rightC == null || offset < -1 || 889 offset > getSize().height) { 890 // Don't allow dragging. 891 maxX = -1; 892 } 893 else { 894 Insets insets = splitPane.getInsets(); 895 896 if (leftC.isVisible()) { 897 minX = leftC.getMinimumSize().height; 898 if (insets != null) { 899 minX += insets.top; 900 } 901 } 902 else { 903 minX = 0; 904 } 905 if (rightC.isVisible()) { 906 int bottom = (insets != null) ? insets.bottom : 0; 907 908 maxX = Math.max(0, splitPane.getSize().height - 909 (getSize().height + bottom) - 910 rightC.getMinimumSize().height); 911 } 912 else { 913 int bottom = (insets != null) ? insets.bottom : 0; 914 915 maxX = Math.max(0, splitPane.getSize().height - 916 (getSize().height + bottom)); 917 } 918 if (maxX < minX) minX = maxX = 0; 919 } 920 } 921 922 923 /** 924 * Returns the y argument, since this is used for vertical 925 * splits. 926 */ 927 protected int getNeededLocation(int x, int y) { 928 int newY; 929 930 newY = Math.min(maxX, Math.max(minX, y - offset)); 931 return newY; 932 } 933 934 935 /** 936 * Returns the new position to put the divider at based on 937 * the passed in MouseEvent. 938 */ 939 protected int positionForMouseEvent(MouseEvent e) { 940 int newY = (e.getSource() == BasicSplitPaneDivider.this) ? 941 (e.getY() + getLocation().y) : e.getY(); 942 943 944 newY = Math.min(maxX, Math.max(minX, newY - offset)); 945 return newY; 946 } 947 } // End of BasicSplitPaneDividier.VerticalDragController 948 949 950 /** 951 * Used to layout a <code>BasicSplitPaneDivider</code>. 952 * Layout for the divider 953 * involves appropriately moving the left/right buttons around. 954 * 955 */ 956 protected class DividerLayout implements LayoutManager 957 { 958 public void layoutContainer(Container c) { 959 if (leftButton != null && rightButton != null && 960 c == BasicSplitPaneDivider.this) { 961 if (splitPane.isOneTouchExpandable()) { 962 Insets insets = getInsets(); 963 964 if (orientation == JSplitPane.VERTICAL_SPLIT) { 965 int extraX = (insets != null) ? insets.left : 0; 966 int blockSize = getHeight(); 967 968 if (insets != null) { 969 blockSize -= (insets.top + insets.bottom); 970 blockSize = Math.max(blockSize, 0); 971 } 972 blockSize = Math.min(blockSize, oneTouchSize); 973 974 int y = (c.getSize().height - blockSize) / 2; 975 976 if (!centerOneTouchButtons) { 977 y = (insets != null) ? insets.top : 0; 978 extraX = 0; 979 } 980 leftButton.setBounds(extraX + oneTouchOffset, y, 981 blockSize * 2, blockSize); 982 rightButton.setBounds(extraX + oneTouchOffset + 983 oneTouchSize * 2, y, 984 blockSize * 2, blockSize); 985 } 986 else { 987 int extraY = (insets != null) ? insets.top : 0; 988 int blockSize = getWidth(); 989 990 if (insets != null) { 991 blockSize -= (insets.left + insets.right); 992 blockSize = Math.max(blockSize, 0); 993 } 994 blockSize = Math.min(blockSize, oneTouchSize); 995 996 int x = (c.getSize().width - blockSize) / 2; 997 998 if (!centerOneTouchButtons) { 999 x = (insets != null) ? insets.left : 0; 1000 extraY = 0; 1001 } 1002 1003 leftButton.setBounds(x, extraY + oneTouchOffset, 1004 blockSize, blockSize * 2); 1005 rightButton.setBounds(x, extraY + oneTouchOffset + 1006 oneTouchSize * 2, blockSize, 1007 blockSize * 2); 1008 } 1009 } 1010 else { 1011 leftButton.setBounds(-5, -5, 1, 1); 1012 rightButton.setBounds(-5, -5, 1, 1); 1013 } 1014 } 1015 } 1016 1017 1018 public Dimension minimumLayoutSize(Container c) { 1019 // NOTE: This isn't really used, refer to 1020 // BasicSplitPaneDivider.getPreferredSize for the reason. 1021 // I leave it in hopes of having this used at some point. 1022 if (c != BasicSplitPaneDivider.this || splitPane == null) { 1023 return new Dimension(0,0); 1024 } 1025 Dimension buttonMinSize = null; 1026 1027 if (splitPane.isOneTouchExpandable() && leftButton != null) { 1028 buttonMinSize = leftButton.getMinimumSize(); 1029 } 1030 1031 Insets insets = getInsets(); 1032 int width = getDividerSize(); 1033 int height = width; 1034 1035 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1036 if (buttonMinSize != null) { 1037 int size = buttonMinSize.height; 1038 if (insets != null) { 1039 size += insets.top + insets.bottom; 1040 } 1041 height = Math.max(height, size); 1042 } 1043 width = 1; 1044 } 1045 else { 1046 if (buttonMinSize != null) { 1047 int size = buttonMinSize.width; 1048 if (insets != null) { 1049 size += insets.left + insets.right; 1050 } 1051 width = Math.max(width, size); 1052 } 1053 height = 1; 1054 } 1055 return new Dimension(width, height); 1056 } 1057 1058 1059 public Dimension preferredLayoutSize(Container c) { 1060 return minimumLayoutSize(c); 1061 } 1062 1063 1064 public void removeLayoutComponent(Component c) {} 1065 1066 public void addLayoutComponent(String string, Component c) {} 1067 } // End of class BasicSplitPaneDivider.DividerLayout 1068 1069 1070 /** 1071 * Listeners installed on the one touch expandable buttons. 1072 */ 1073 private class OneTouchActionHandler implements ActionListener { 1074 /** True indicates the resize should go the minimum (top or left) 1075 * vs false which indicates the resize should go to the maximum. 1076 */ 1077 private boolean toMinimum; 1078 1079 OneTouchActionHandler(boolean toMinimum) { 1080 this.toMinimum = toMinimum; 1081 } 1082 1083 public void actionPerformed(ActionEvent e) { 1084 Insets insets = splitPane.getInsets(); 1085 int lastLoc = splitPane.getLastDividerLocation(); 1086 int currentLoc = splitPaneUI.getDividerLocation(splitPane); 1087 int newLoc; 1088 1089 // We use the location from the UI directly, as the location the 1090 // JSplitPane itself maintains is not necessarly correct. 1091 if (toMinimum) { 1092 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1093 if (currentLoc >= (splitPane.getHeight() - 1094 insets.bottom - getHeight())) { 1095 int maxLoc = splitPane.getMaximumDividerLocation(); 1096 newLoc = Math.min(lastLoc, maxLoc); 1097 splitPaneUI.setKeepHidden(false); 1098 } 1099 else { 1100 newLoc = insets.top; 1101 splitPaneUI.setKeepHidden(true); 1102 } 1103 } 1104 else { 1105 if (currentLoc >= (splitPane.getWidth() - 1106 insets.right - getWidth())) { 1107 int maxLoc = splitPane.getMaximumDividerLocation(); 1108 newLoc = Math.min(lastLoc, maxLoc); 1109 splitPaneUI.setKeepHidden(false); 1110 } 1111 else { 1112 newLoc = insets.left; 1113 splitPaneUI.setKeepHidden(true); 1114 } 1115 } 1116 } 1117 else { 1118 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1119 if (currentLoc == insets.top) { 1120 int maxLoc = splitPane.getMaximumDividerLocation(); 1121 newLoc = Math.min(lastLoc, maxLoc); 1122 splitPaneUI.setKeepHidden(false); 1123 } 1124 else { 1125 newLoc = splitPane.getHeight() - getHeight() - 1126 insets.top; 1127 splitPaneUI.setKeepHidden(true); 1128 } 1129 } 1130 else { 1131 if (currentLoc == insets.left) { 1132 int maxLoc = splitPane.getMaximumDividerLocation(); 1133 newLoc = Math.min(lastLoc, maxLoc); 1134 splitPaneUI.setKeepHidden(false); 1135 } 1136 else { 1137 newLoc = splitPane.getWidth() - getWidth() - 1138 insets.left; 1139 splitPaneUI.setKeepHidden(true); 1140 } 1141 } 1142 } 1143 if (currentLoc != newLoc) { 1144 splitPane.setDividerLocation(newLoc); 1145 // We do this in case the dividers notion of the location 1146 // differs from the real location. 1147 splitPane.setLastDividerLocation(currentLoc); 1148 } 1149 } 1150 } // End of class BasicSplitPaneDivider.LeftActionListener 1151 }