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