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