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