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 public boolean isFocusTraversable() { 445 return false; 446 } 447 }; 448 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize)); 449 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 450 b.setFocusPainted(false); 451 b.setBorderPainted(false); 452 b.setRequestFocusEnabled(false); 453 return b; 454 } 455 456 457 /** 458 * Creates and return an instance of {@code JButton} that can be used to 459 * collapse the right component in the split pane. 460 * 461 * @return an instance of {@code JButton} 462 */ 463 protected JButton createRightOneTouchButton() { 464 JButton b = new JButton() { 465 public void setBorder(Border border) { 466 } 467 public void paint(Graphics g) { 468 if (splitPane != null) { 469 int[] xs = new int[3]; 470 int[] ys = new int[3]; 471 int blockSize; 472 473 // Fill the background first ... 474 g.setColor(this.getBackground()); 475 g.fillRect(0, 0, this.getWidth(), 476 this.getHeight()); 477 478 // ... then draw the arrow. 479 if (orientation == JSplitPane.VERTICAL_SPLIT) { 480 blockSize = Math.min(getHeight(), oneTouchSize); 481 xs[0] = blockSize; 482 xs[1] = blockSize << 1; 483 xs[2] = 0; 484 ys[0] = blockSize; 485 ys[1] = ys[2] = 0; 486 } 487 else { 488 blockSize = Math.min(getWidth(), oneTouchSize); 489 xs[0] = xs[2] = 0; 490 xs[1] = blockSize; 491 ys[0] = 0; 492 ys[1] = blockSize; 493 ys[2] = blockSize << 1; 494 } 495 g.setColor(Color.black); 496 g.fillPolygon(xs, ys, 3); 497 } 498 } 499 // Don't want the button to participate in focus traversable. 500 public boolean isFocusTraversable() { 501 return false; 502 } 503 }; 504 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize)); 505 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 506 b.setFocusPainted(false); 507 b.setBorderPainted(false); 508 b.setRequestFocusEnabled(false); 509 return b; 510 } 511 512 513 /** 514 * Message to prepare for dragging. This messages the BasicSplitPaneUI 515 * with startDragging. 516 */ 517 protected void prepareForDragging() { 518 splitPaneUI.startDragging(); 519 } 520 521 522 /** 523 * Messages the BasicSplitPaneUI with dragDividerTo that this instance 524 * is contained in. 525 * 526 * @param location a location 527 */ 528 protected void dragDividerTo(int location) { 529 splitPaneUI.dragDividerTo(location); 530 } 531 532 533 /** 534 * Messages the BasicSplitPaneUI with finishDraggingTo that this instance 535 * is contained in. 536 * 537 * @param location a location 538 */ 539 protected void finishDraggingTo(int location) { 540 splitPaneUI.finishDraggingTo(location); 541 } 542 543 544 /** 545 * MouseHandler is responsible for converting mouse events 546 * (released, dragged...) into the appropriate DragController 547 * methods. 548 * 549 */ 550 protected class MouseHandler extends MouseAdapter 551 implements MouseMotionListener 552 { 553 /** 554 * Starts the dragging session by creating the appropriate instance 555 * of DragController. 556 */ 557 public void mousePressed(MouseEvent e) { 558 if ((e.getSource() == BasicSplitPaneDivider.this || 559 e.getSource() == splitPane) && 560 dragger == null &&splitPane.isEnabled()) { 561 Component newHiddenDivider = splitPaneUI. 562 getNonContinuousLayoutDivider(); 563 564 if (hiddenDivider != newHiddenDivider) { 565 if (hiddenDivider != null) { 566 hiddenDivider.removeMouseListener(this); 567 hiddenDivider.removeMouseMotionListener(this); 568 } 569 hiddenDivider = newHiddenDivider; 570 if (hiddenDivider != null) { 571 hiddenDivider.addMouseMotionListener(this); 572 hiddenDivider.addMouseListener(this); 573 } 574 } 575 if (splitPane.getLeftComponent() != null && 576 splitPane.getRightComponent() != null) { 577 if (orientation == JSplitPane.HORIZONTAL_SPLIT) { 578 dragger = new DragController(e); 579 } 580 else { 581 dragger = new VerticalDragController(e); 582 } 583 if (!dragger.isValid()) { 584 dragger = null; 585 } 586 else { 587 prepareForDragging(); 588 dragger.continueDrag(e); 589 } 590 } 591 e.consume(); 592 } 593 } 594 595 596 /** 597 * If dragger is not null it is messaged with completeDrag. 598 */ 599 public void mouseReleased(MouseEvent e) { 600 if (dragger != null) { 601 if (e.getSource() == splitPane) { 602 dragger.completeDrag(e.getX(), e.getY()); 603 } 604 else if (e.getSource() == BasicSplitPaneDivider.this) { 605 Point ourLoc = getLocation(); 606 607 dragger.completeDrag(e.getX() + ourLoc.x, 608 e.getY() + ourLoc.y); 609 } 610 else if (e.getSource() == hiddenDivider) { 611 Point hDividerLoc = hiddenDivider.getLocation(); 612 int ourX = e.getX() + hDividerLoc.x; 613 int ourY = e.getY() + hDividerLoc.y; 614 615 dragger.completeDrag(ourX, ourY); 616 } 617 dragger = null; 618 e.consume(); 619 } 620 } 621 622 623 // 624 // MouseMotionListener 625 // 626 627 /** 628 * If dragger is not null it is messaged with continueDrag. 629 */ 630 public void mouseDragged(MouseEvent e) { 631 if (dragger != null) { 632 if (e.getSource() == splitPane) { 633 dragger.continueDrag(e.getX(), e.getY()); 634 } 635 else if (e.getSource() == BasicSplitPaneDivider.this) { 636 Point ourLoc = getLocation(); 637 638 dragger.continueDrag(e.getX() + ourLoc.x, 639 e.getY() + ourLoc.y); 640 } 641 else if (e.getSource() == hiddenDivider) { 642 Point hDividerLoc = hiddenDivider.getLocation(); 643 int ourX = e.getX() + hDividerLoc.x; 644 int ourY = e.getY() + hDividerLoc.y; 645 646 dragger.continueDrag(ourX, ourY); 647 } 648 e.consume(); 649 } 650 } 651 652 653 /** 654 * Resets the cursor based on the orientation. 655 */ 656 public void mouseMoved(MouseEvent e) { 657 } 658 659 /** 660 * Invoked when the mouse enters a component. 661 * 662 * @param e MouseEvent describing the details of the enter event. 663 * @since 1.5 664 */ 665 public void mouseEntered(MouseEvent e) { 666 if (e.getSource() == BasicSplitPaneDivider.this) { 667 setMouseOver(true); 668 } 669 } 670 671 /** 672 * Invoked when the mouse exits a component. 673 * 674 * @param e MouseEvent describing the details of the exit event. 675 * @since 1.5 676 */ 677 public void mouseExited(MouseEvent e) { 678 if (e.getSource() == BasicSplitPaneDivider.this) { 679 setMouseOver(false); 680 } 681 } 682 } 683 684 685 /** 686 * Handles the events during a dragging session for a 687 * HORIZONTAL_SPLIT oriented split pane. This continually 688 * messages <code>dragDividerTo</code> and then when done messages 689 * <code>finishDraggingTo</code>. When an instance is created it should be 690 * messaged with <code>isValid</code> to insure that dragging can happen 691 * (dragging won't be allowed if the two views can not be resized). 692 * <p> 693 * <strong>Warning:</strong> 694 * Serialized objects of this class will not be compatible with 695 * future Swing releases. The current serialization support is 696 * appropriate for short term storage or RMI between applications running 697 * the same version of Swing. As of 1.4, support for long term storage 698 * of all JavaBeans™ 699 * has been added to the <code>java.beans</code> package. 700 * Please see {@link java.beans.XMLEncoder}. 701 */ 702 @SuppressWarnings("serial") // Same-version serialization only 703 protected class DragController 704 { 705 /** 706 * Initial location of the divider. 707 */ 708 int initialX; 709 710 /** 711 * Maximum and minimum positions to drag to. 712 */ 713 int maxX, minX; 714 715 /** 716 * Initial location the mouse down happened at. 717 */ 718 int offset; 719 720 /** 721 * Constructs a new instance of {@code DragController}. 722 * 723 * @param e a mouse event 724 */ 725 protected DragController(MouseEvent e) { 726 JSplitPane splitPane = splitPaneUI.getSplitPane(); 727 Component leftC = splitPane.getLeftComponent(); 728 Component rightC = splitPane.getRightComponent(); 729 730 initialX = getLocation().x; 731 if (e.getSource() == BasicSplitPaneDivider.this) { 732 offset = e.getX(); 733 } 734 else { // splitPane 735 offset = e.getX() - initialX; 736 } 737 if (leftC == null || rightC == null || offset < -1 || 738 offset >= getSize().width) { 739 // Don't allow dragging. 740 maxX = -1; 741 } 742 else { 743 Insets insets = splitPane.getInsets(); 744 745 if (leftC.isVisible()) { 746 minX = leftC.getMinimumSize().width; 747 if (insets != null) { 748 minX += insets.left; 749 } 750 } 751 else { 752 minX = 0; 753 } 754 if (rightC.isVisible()) { 755 int right = (insets != null) ? insets.right : 0; 756 maxX = Math.max(0, splitPane.getSize().width - 757 (getSize().width + right) - 758 rightC.getMinimumSize().width); 759 } 760 else { 761 int right = (insets != null) ? insets.right : 0; 762 maxX = Math.max(0, splitPane.getSize().width - 763 (getSize().width + right)); 764 } 765 if (maxX < minX) minX = maxX = 0; 766 } 767 } 768 769 770 /** 771 * Returns {@code true} if the dragging session is valid. 772 * 773 * @return {@code true} if the dragging session is valid 774 */ 775 protected boolean isValid() { 776 return (maxX > 0); 777 } 778 779 780 /** 781 * Returns the new position to put the divider at based on 782 * the passed in MouseEvent. 783 * 784 * @param e a mouse event 785 * @return the new position 786 */ 787 protected int positionForMouseEvent(MouseEvent e) { 788 int newX = (e.getSource() == BasicSplitPaneDivider.this) ? 789 (e.getX() + getLocation().x) : e.getX(); 790 791 newX = Math.min(maxX, Math.max(minX, newX - offset)); 792 return newX; 793 } 794 795 796 /** 797 * Returns the x argument, since this is used for horizontal 798 * splits. 799 * 800 * @param x an X coordinate 801 * @param y an Y coordinate 802 * @return the X argument 803 */ 804 protected int getNeededLocation(int x, int y) { 805 int newX; 806 807 newX = Math.min(maxX, Math.max(minX, x - offset)); 808 return newX; 809 } 810 811 /** 812 * Messages dragDividerTo with the new location for the mouse 813 * event. 814 * 815 * @param newX an X coordinate 816 * @param newY an Y coordinate 817 */ 818 protected void continueDrag(int newX, int newY) { 819 dragDividerTo(getNeededLocation(newX, newY)); 820 } 821 822 823 /** 824 * Messages dragDividerTo with the new location for the mouse 825 * event. 826 * 827 * @param e a mouse event 828 */ 829 protected void continueDrag(MouseEvent e) { 830 dragDividerTo(positionForMouseEvent(e)); 831 } 832 833 /** 834 * Messages finishDraggingTo with the new location for the mouse 835 * event. 836 * 837 * @param x an X coordinate 838 * @param y an Y coordinate 839 */ 840 protected void completeDrag(int x, int y) { 841 finishDraggingTo(getNeededLocation(x, y)); 842 } 843 844 845 /** 846 * Messages finishDraggingTo with the new location for the mouse 847 * event. 848 * 849 * @param e a mouse event 850 */ 851 protected void completeDrag(MouseEvent e) { 852 finishDraggingTo(positionForMouseEvent(e)); 853 } 854 } // End of BasicSplitPaneDivider.DragController 855 856 857 /** 858 * Handles the events during a dragging session for a 859 * VERTICAL_SPLIT oriented split pane. This continually 860 * messages <code>dragDividerTo</code> and then when done messages 861 * <code>finishDraggingTo</code>. When an instance is created it should be 862 * messaged with <code>isValid</code> to insure that dragging can happen 863 * (dragging won't be allowed if the two views can not be resized). 864 */ 865 protected class VerticalDragController extends DragController 866 { 867 /* DragControllers ivars are now in terms of y, not x. */ 868 /** 869 * Constructs a new instance of {@code VerticalDragController}. 870 * 871 * @param e a mouse event 872 */ 873 protected VerticalDragController(MouseEvent e) { 874 super(e); 875 JSplitPane splitPane = splitPaneUI.getSplitPane(); 876 Component leftC = splitPane.getLeftComponent(); 877 Component rightC = splitPane.getRightComponent(); 878 879 initialX = getLocation().y; 880 if (e.getSource() == BasicSplitPaneDivider.this) { 881 offset = e.getY(); 882 } 883 else { 884 offset = e.getY() - initialX; 885 } 886 if (leftC == null || rightC == null || offset < -1 || 887 offset > getSize().height) { 888 // Don't allow dragging. 889 maxX = -1; 890 } 891 else { 892 Insets insets = splitPane.getInsets(); 893 894 if (leftC.isVisible()) { 895 minX = leftC.getMinimumSize().height; 896 if (insets != null) { 897 minX += insets.top; 898 } 899 } 900 else { 901 minX = 0; 902 } 903 if (rightC.isVisible()) { 904 int bottom = (insets != null) ? insets.bottom : 0; 905 906 maxX = Math.max(0, splitPane.getSize().height - 907 (getSize().height + bottom) - 908 rightC.getMinimumSize().height); 909 } 910 else { 911 int bottom = (insets != null) ? insets.bottom : 0; 912 913 maxX = Math.max(0, splitPane.getSize().height - 914 (getSize().height + bottom)); 915 } 916 if (maxX < minX) minX = maxX = 0; 917 } 918 } 919 920 921 /** 922 * Returns the y argument, since this is used for vertical 923 * splits. 924 */ 925 protected int getNeededLocation(int x, int y) { 926 int newY; 927 928 newY = Math.min(maxX, Math.max(minX, y - offset)); 929 return newY; 930 } 931 932 933 /** 934 * Returns the new position to put the divider at based on 935 * the passed in MouseEvent. 936 */ 937 protected int positionForMouseEvent(MouseEvent e) { 938 int newY = (e.getSource() == BasicSplitPaneDivider.this) ? 939 (e.getY() + getLocation().y) : e.getY(); 940 941 942 newY = Math.min(maxX, Math.max(minX, newY - offset)); 943 return newY; 944 } 945 } // End of BasicSplitPaneDividier.VerticalDragController 946 947 948 /** 949 * Used to layout a <code>BasicSplitPaneDivider</code>. 950 * Layout for the divider 951 * involves appropriately moving the left/right buttons around. 952 * 953 */ 954 protected class DividerLayout implements LayoutManager 955 { 956 public void layoutContainer(Container c) { 957 if (leftButton != null && rightButton != null && 958 c == BasicSplitPaneDivider.this) { 959 if (splitPane.isOneTouchExpandable()) { 960 Insets insets = getInsets(); 961 962 if (orientation == JSplitPane.VERTICAL_SPLIT) { 963 int extraX = (insets != null) ? insets.left : 0; 964 int blockSize = getHeight(); 965 966 if (insets != null) { 967 blockSize -= (insets.top + insets.bottom); 968 blockSize = Math.max(blockSize, 0); 969 } 970 blockSize = Math.min(blockSize, oneTouchSize); 971 972 int y = (c.getSize().height - blockSize) / 2; 973 974 if (!centerOneTouchButtons) { 975 y = (insets != null) ? insets.top : 0; 976 extraX = 0; 977 } 978 leftButton.setBounds(extraX + oneTouchOffset, y, 979 blockSize * 2, blockSize); 980 rightButton.setBounds(extraX + oneTouchOffset + 981 oneTouchSize * 2, y, 982 blockSize * 2, blockSize); 983 } 984 else { 985 int extraY = (insets != null) ? insets.top : 0; 986 int blockSize = getWidth(); 987 988 if (insets != null) { 989 blockSize -= (insets.left + insets.right); 990 blockSize = Math.max(blockSize, 0); 991 } 992 blockSize = Math.min(blockSize, oneTouchSize); 993 994 int x = (c.getSize().width - blockSize) / 2; 995 996 if (!centerOneTouchButtons) { 997 x = (insets != null) ? insets.left : 0; 998 extraY = 0; 999 } 1000 1001 leftButton.setBounds(x, extraY + oneTouchOffset, 1002 blockSize, blockSize * 2); 1003 rightButton.setBounds(x, extraY + oneTouchOffset + 1004 oneTouchSize * 2, blockSize, 1005 blockSize * 2); 1006 } 1007 } 1008 else { 1009 leftButton.setBounds(-5, -5, 1, 1); 1010 rightButton.setBounds(-5, -5, 1, 1); 1011 } 1012 } 1013 } 1014 1015 1016 public Dimension minimumLayoutSize(Container c) { 1017 // NOTE: This isn't really used, refer to 1018 // BasicSplitPaneDivider.getPreferredSize for the reason. 1019 // I leave it in hopes of having this used at some point. 1020 if (c != BasicSplitPaneDivider.this || splitPane == null) { 1021 return new Dimension(0,0); 1022 } 1023 Dimension buttonMinSize = null; 1024 1025 if (splitPane.isOneTouchExpandable() && leftButton != null) { 1026 buttonMinSize = leftButton.getMinimumSize(); 1027 } 1028 1029 Insets insets = getInsets(); 1030 int width = getDividerSize(); 1031 int height = width; 1032 1033 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1034 if (buttonMinSize != null) { 1035 int size = buttonMinSize.height; 1036 if (insets != null) { 1037 size += insets.top + insets.bottom; 1038 } 1039 height = Math.max(height, size); 1040 } 1041 width = 1; 1042 } 1043 else { 1044 if (buttonMinSize != null) { 1045 int size = buttonMinSize.width; 1046 if (insets != null) { 1047 size += insets.left + insets.right; 1048 } 1049 width = Math.max(width, size); 1050 } 1051 height = 1; 1052 } 1053 return new Dimension(width, height); 1054 } 1055 1056 1057 public Dimension preferredLayoutSize(Container c) { 1058 return minimumLayoutSize(c); 1059 } 1060 1061 1062 public void removeLayoutComponent(Component c) {} 1063 1064 public void addLayoutComponent(String string, Component c) {} 1065 } // End of class BasicSplitPaneDivider.DividerLayout 1066 1067 1068 /** 1069 * Listeners installed on the one touch expandable buttons. 1070 */ 1071 private class OneTouchActionHandler implements ActionListener { 1072 /** True indicates the resize should go the minimum (top or left) 1073 * vs false which indicates the resize should go to the maximum. 1074 */ 1075 private boolean toMinimum; 1076 1077 OneTouchActionHandler(boolean toMinimum) { 1078 this.toMinimum = toMinimum; 1079 } 1080 1081 public void actionPerformed(ActionEvent e) { 1082 Insets insets = splitPane.getInsets(); 1083 int lastLoc = splitPane.getLastDividerLocation(); 1084 int currentLoc = splitPaneUI.getDividerLocation(splitPane); 1085 int newLoc; 1086 1087 // We use the location from the UI directly, as the location the 1088 // JSplitPane itself maintains is not necessarly correct. 1089 if (toMinimum) { 1090 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1091 if (currentLoc >= (splitPane.getHeight() - 1092 insets.bottom - getHeight())) { 1093 int maxLoc = splitPane.getMaximumDividerLocation(); 1094 newLoc = Math.min(lastLoc, maxLoc); 1095 splitPaneUI.setKeepHidden(false); 1096 } 1097 else { 1098 newLoc = insets.top; 1099 splitPaneUI.setKeepHidden(true); 1100 } 1101 } 1102 else { 1103 if (currentLoc >= (splitPane.getWidth() - 1104 insets.right - getWidth())) { 1105 int maxLoc = splitPane.getMaximumDividerLocation(); 1106 newLoc = Math.min(lastLoc, maxLoc); 1107 splitPaneUI.setKeepHidden(false); 1108 } 1109 else { 1110 newLoc = insets.left; 1111 splitPaneUI.setKeepHidden(true); 1112 } 1113 } 1114 } 1115 else { 1116 if (orientation == JSplitPane.VERTICAL_SPLIT) { 1117 if (currentLoc == insets.top) { 1118 int maxLoc = splitPane.getMaximumDividerLocation(); 1119 newLoc = Math.min(lastLoc, maxLoc); 1120 splitPaneUI.setKeepHidden(false); 1121 } 1122 else { 1123 newLoc = splitPane.getHeight() - getHeight() - 1124 insets.top; 1125 splitPaneUI.setKeepHidden(true); 1126 } 1127 } 1128 else { 1129 if (currentLoc == insets.left) { 1130 int maxLoc = splitPane.getMaximumDividerLocation(); 1131 newLoc = Math.min(lastLoc, maxLoc); 1132 splitPaneUI.setKeepHidden(false); 1133 } 1134 else { 1135 newLoc = splitPane.getWidth() - getWidth() - 1136 insets.left; 1137 splitPaneUI.setKeepHidden(true); 1138 } 1139 } 1140 } 1141 if (currentLoc != newLoc) { 1142 splitPane.setDividerLocation(newLoc); 1143 // We do this in case the dividers notion of the location 1144 // differs from the real location. 1145 splitPane.setLastDividerLocation(currentLoc); 1146 } 1147 } 1148 } // End of class BasicSplitPaneDivider.LeftActionListener 1149 }