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&trade;
  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&trade;
 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 }