1 /*
2 * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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<sup><font size="-2">TM</font></sup>
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 public class BasicSplitPaneDivider extends Container
63 implements PropertyChangeListener
64 {
65 /**
66 * Width or height of the divider based on orientation
67 * BasicSplitPaneUI adds two to this.
68 */
69 protected static final int ONE_TOUCH_SIZE = 6;
70 protected static final int ONE_TOUCH_OFFSET = 2;
71
72 /**
73 * Handles mouse dragging message to do the actual dragging.
74 */
75 protected DragController dragger;
76
77 /**
78 * UI this instance was created from.
79 */
80 protected BasicSplitPaneUI splitPaneUI;
81
82 /**
83 * Size of the divider.
84 */
85 protected int dividerSize = 0; // default - SET TO 0???
86
87 /**
88 * Divider that is used for noncontinuous layout mode.
89 */
90 protected Component hiddenDivider;
91
92 /**
93 * JSplitPane the receiver is contained in.
94 */
95 protected JSplitPane splitPane;
96
97 /**
98 * Handles mouse events from both this class, and the split pane.
99 * Mouse events are handled for the splitpane since you want to be able
100 * to drag when clicking on the border of the divider, which is not
101 * drawn by the divider.
102 */
103 protected MouseHandler mouseHandler;
104
105 /**
106 * Orientation of the JSplitPane.
107 */
108 protected int orientation;
109
110 /**
111 * Button for quickly toggling the left component.
112 */
113 protected JButton leftButton;
114
115 /**
116 * Button for quickly toggling the right component.
117 */
118 protected JButton rightButton;
119
120 /** Border. */
121 private Border border;
122
123 /**
124 * Is the mouse over the divider?
125 */
126 private boolean mouseOver;
127
128 private int oneTouchSize;
129 private int oneTouchOffset;
130
131 /**
132 * If true the one touch buttons are centered on the divider.
133 */
134 private boolean centerOneTouchButtons;
135
136
137 /**
138 * Creates an instance of BasicSplitPaneDivider. Registers this
139 * instance for mouse events and mouse dragged events.
140 */
141 public BasicSplitPaneDivider(BasicSplitPaneUI ui) {
142 oneTouchSize = DefaultLookup.getInt(ui.getSplitPane(), ui,
143 "SplitPane.oneTouchButtonSize", ONE_TOUCH_SIZE);
144 oneTouchOffset = DefaultLookup.getInt(ui.getSplitPane(), ui,
145 "SplitPane.oneTouchButtonOffset", ONE_TOUCH_OFFSET);
146 centerOneTouchButtons = DefaultLookup.getBoolean(ui.getSplitPane(),
147 ui, "SplitPane.centerOneTouchButtons", true);
148 setLayout(new DividerLayout());
149 setBasicSplitPaneUI(ui);
150 orientation = splitPane.getOrientation();
151 setCursor((orientation == JSplitPane.HORIZONTAL_SPLIT) ?
152 Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) :
153 Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
154 setBackground(UIManager.getColor("SplitPane.background"));
155 }
156
157 private void revalidate() {
158 invalidate();
159 if (splitPane != null) {
160 splitPane.revalidate();
161 }
162 }
163
164 /**
165 * Sets the SplitPaneUI that is using the receiver.
166 */
167 public void setBasicSplitPaneUI(BasicSplitPaneUI newUI) {
168 if (splitPane != null) {
169 splitPane.removePropertyChangeListener(this);
170 if (mouseHandler != null) {
171 splitPane.removeMouseListener(mouseHandler);
172 splitPane.removeMouseMotionListener(mouseHandler);
173 removeMouseListener(mouseHandler);
174 removeMouseMotionListener(mouseHandler);
175 mouseHandler = null;
176 }
177 }
178 splitPaneUI = newUI;
179 if (newUI != null) {
180 splitPane = newUI.getSplitPane();
181 if (splitPane != null) {
182 if (mouseHandler == null) mouseHandler = new MouseHandler();
183 splitPane.addMouseListener(mouseHandler);
184 splitPane.addMouseMotionListener(mouseHandler);
185 addMouseListener(mouseHandler);
186 addMouseMotionListener(mouseHandler);
187 splitPane.addPropertyChangeListener(this);
188 if (splitPane.isOneTouchExpandable()) {
189 oneTouchExpandableChanged();
190 }
191 }
192 }
193 else {
194 splitPane = null;
195 }
196 }
197
198
199 /**
200 * Returns the <code>SplitPaneUI</code> the receiver is currently
201 * in.
202 */
203 public BasicSplitPaneUI getBasicSplitPaneUI() {
204 return splitPaneUI;
205 }
206
207
208 /**
209 * Sets the size of the divider to <code>newSize</code>. That is
210 * the width if the splitpane is <code>HORIZONTAL_SPLIT</code>, or
211 * the height of <code>VERTICAL_SPLIT</code>.
212 */
213 public void setDividerSize(int newSize) {
214 dividerSize = newSize;
215 }
216
217
218 /**
219 * Returns the size of the divider, that is the width if the splitpane
220 * is HORIZONTAL_SPLIT, or the height of VERTICAL_SPLIT.
221 */
222 public int getDividerSize() {
223 return dividerSize;
224 }
225
226
227 /**
228 * Sets the border of this component.
229 * @since 1.3
230 */
231 public void setBorder(Border border) {
232 Border oldBorder = this.border;
233
234 this.border = border;
235 }
236
237 /**
238 * Returns the border of this component or null if no border is
239 * currently set.
240 *
241 * @return the border object for this component
242 * @see #setBorder
243 * @since 1.3
244 */
245 public Border getBorder() {
246 return border;
247 }
248
249 /**
250 * If a border has been set on this component, returns the
251 * border's insets, else calls super.getInsets.
252 *
253 * @return the value of the insets property.
254 * @see #setBorder
255 */
256 public Insets getInsets() {
257 Border border = getBorder();
258
259 if (border != null) {
260 return border.getBorderInsets(this);
261 }
262 return super.getInsets();
263 }
264
265 /**
266 * Sets whether or not the mouse is currently over the divider.
267 *
268 * @param mouseOver whether or not the mouse is currently over the divider
269 * @since 1.5
270 */
271 protected void setMouseOver(boolean mouseOver) {
272 this.mouseOver = mouseOver;
273 }
274
275 /**
276 * Returns whether or not the mouse is currently over the divider
277 *
278 * @return whether or not the mouse is currently over the divider
279 * @since 1.5
280 */
281 public boolean isMouseOver() {
282 return mouseOver;
283 }
284
285 /**
286 * Returns dividerSize x dividerSize
287 */
288 public Dimension getPreferredSize() {
289 // Ideally this would return the size from the layout manager,
290 // but that could result in the layed out size being different from
291 // the dividerSize, which may break developers as well as
292 // BasicSplitPaneUI.
293 if (orientation == JSplitPane.HORIZONTAL_SPLIT) {
294 return new Dimension(getDividerSize(), 1);
295 }
296 return new Dimension(1, getDividerSize());
297 }
298
299 /**
300 * Returns dividerSize x dividerSize
301 */
302 public Dimension getMinimumSize() {
303 return getPreferredSize();
304 }
305
306
307 /**
308 * Property change event, presumably from the JSplitPane, will message
309 * updateOrientation if necessary.
310 */
311 public void propertyChange(PropertyChangeEvent e) {
312 if (e.getSource() == splitPane) {
313 if (e.getPropertyName() == JSplitPane.ORIENTATION_PROPERTY) {
314 orientation = splitPane.getOrientation();
315 setCursor((orientation == JSplitPane.HORIZONTAL_SPLIT) ?
316 Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) :
317 Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
318 revalidate();
319 }
320 else if (e.getPropertyName() == JSplitPane.
321 ONE_TOUCH_EXPANDABLE_PROPERTY) {
322 oneTouchExpandableChanged();
323 }
324 }
325 }
326
327
328 /**
329 * Paints the divider.
330 */
331 public void paint(Graphics g) {
332 super.paint(g);
333
334 // Paint the border.
335 Border border = getBorder();
336
337 if (border != null) {
338 Dimension size = getSize();
339
340 border.paintBorder(this, g, 0, 0, size.width, size.height);
341 }
342 }
343
344
345 /**
346 * Messaged when the oneTouchExpandable value of the JSplitPane the
347 * receiver is contained in changes. Will create the
348 * <code>leftButton</code> and <code>rightButton</code> if they
349 * are null. invalidates the receiver as well.
350 */
351 protected void oneTouchExpandableChanged() {
352 if (!DefaultLookup.getBoolean(splitPane, splitPaneUI,
353 "SplitPane.supportsOneTouchButtons", true)) {
354 // Look and feel doesn't want to support one touch buttons, bail.
355 return;
356 }
357 if (splitPane.isOneTouchExpandable() &&
358 leftButton == null &&
359 rightButton == null) {
360 /* Create the left button and add an action listener to
361 expand/collapse it. */
362 leftButton = createLeftOneTouchButton();
363 if (leftButton != null)
364 leftButton.addActionListener(new OneTouchActionHandler(true));
365
366
367 /* Create the right button and add an action listener to
368 expand/collapse it. */
369 rightButton = createRightOneTouchButton();
370 if (rightButton != null)
371 rightButton.addActionListener(new OneTouchActionHandler
372 (false));
373
374 if (leftButton != null && rightButton != null) {
375 add(leftButton);
376 add(rightButton);
377 }
378 }
379 revalidate();
380 }
381
382
383 /**
384 * Creates and return an instance of JButton that can be used to
385 * collapse the left component in the split pane.
386 */
387 protected JButton createLeftOneTouchButton() {
388 JButton b = new JButton() {
389 public void setBorder(Border b) {
390 }
391 public void paint(Graphics g) {
392 if (splitPane != null) {
393 int[] xs = new int[3];
394 int[] ys = new int[3];
395 int blockSize;
396
397 // Fill the background first ...
398 g.setColor(this.getBackground());
399 g.fillRect(0, 0, this.getWidth(),
400 this.getHeight());
401
402 // ... then draw the arrow.
403 g.setColor(Color.black);
404 if (orientation == JSplitPane.VERTICAL_SPLIT) {
405 blockSize = Math.min(getHeight(), oneTouchSize);
406 xs[0] = blockSize;
407 xs[1] = 0;
408 xs[2] = blockSize << 1;
409 ys[0] = 0;
410 ys[1] = ys[2] = blockSize;
411 g.drawPolygon(xs, ys, 3); // Little trick to make the
412 // arrows of equal size
413 }
414 else {
415 blockSize = Math.min(getWidth(), oneTouchSize);
416 xs[0] = xs[2] = blockSize;
417 xs[1] = 0;
418 ys[0] = 0;
419 ys[1] = blockSize;
420 ys[2] = blockSize << 1;
421 }
422 g.fillPolygon(xs, ys, 3);
423 }
424 }
425 // Don't want the button to participate in focus traversable.
426 public boolean isFocusTraversable() {
427 return false;
428 }
429 };
430 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize));
431 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
432 b.setFocusPainted(false);
433 b.setBorderPainted(false);
434 b.setRequestFocusEnabled(false);
435 return b;
436 }
437
438
439 /**
440 * Creates and return an instance of JButton that can be used to
441 * collapse the right component in the split pane.
442 */
443 protected JButton createRightOneTouchButton() {
444 JButton b = new JButton() {
445 public void setBorder(Border border) {
446 }
447 public void paint(Graphics g) {
448 if (splitPane != null) {
449 int[] xs = new int[3];
450 int[] ys = new int[3];
451 int blockSize;
452
453 // Fill the background first ...
454 g.setColor(this.getBackground());
455 g.fillRect(0, 0, this.getWidth(),
456 this.getHeight());
457
458 // ... then draw the arrow.
459 if (orientation == JSplitPane.VERTICAL_SPLIT) {
460 blockSize = Math.min(getHeight(), oneTouchSize);
461 xs[0] = blockSize;
462 xs[1] = blockSize << 1;
463 xs[2] = 0;
464 ys[0] = blockSize;
465 ys[1] = ys[2] = 0;
466 }
467 else {
468 blockSize = Math.min(getWidth(), oneTouchSize);
469 xs[0] = xs[2] = 0;
470 xs[1] = blockSize;
471 ys[0] = 0;
472 ys[1] = blockSize;
473 ys[2] = blockSize << 1;
474 }
475 g.setColor(Color.black);
476 g.fillPolygon(xs, ys, 3);
477 }
478 }
479 // Don't want the button to participate in focus traversable.
480 public boolean isFocusTraversable() {
481 return false;
482 }
483 };
484 b.setMinimumSize(new Dimension(oneTouchSize, oneTouchSize));
485 b.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
486 b.setFocusPainted(false);
487 b.setBorderPainted(false);
488 b.setRequestFocusEnabled(false);
489 return b;
490 }
491
492
493 /**
494 * Message to prepare for dragging. This messages the BasicSplitPaneUI
495 * with startDragging.
496 */
497 protected void prepareForDragging() {
498 splitPaneUI.startDragging();
499 }
500
501
502 /**
503 * Messages the BasicSplitPaneUI with dragDividerTo that this instance
504 * is contained in.
505 */
506 protected void dragDividerTo(int location) {
507 splitPaneUI.dragDividerTo(location);
508 }
509
510
511 /**
512 * Messages the BasicSplitPaneUI with finishDraggingTo that this instance
513 * is contained in.
514 */
515 protected void finishDraggingTo(int location) {
516 splitPaneUI.finishDraggingTo(location);
517 }
518
519
520 /**
521 * MouseHandler is responsible for converting mouse events
522 * (released, dragged...) into the appropriate DragController
523 * methods.
524 * <p>
525 */
526 protected class MouseHandler extends MouseAdapter
527 implements MouseMotionListener
528 {
529 /**
530 * Starts the dragging session by creating the appropriate instance
531 * of DragController.
532 */
533 public void mousePressed(MouseEvent e) {
534 if ((e.getSource() == BasicSplitPaneDivider.this ||
535 e.getSource() == splitPane) &&
536 dragger == null &&splitPane.isEnabled()) {
537 Component newHiddenDivider = splitPaneUI.
538 getNonContinuousLayoutDivider();
539
540 if (hiddenDivider != newHiddenDivider) {
541 if (hiddenDivider != null) {
542 hiddenDivider.removeMouseListener(this);
543 hiddenDivider.removeMouseMotionListener(this);
544 }
545 hiddenDivider = newHiddenDivider;
546 if (hiddenDivider != null) {
547 hiddenDivider.addMouseMotionListener(this);
548 hiddenDivider.addMouseListener(this);
549 }
550 }
551 if (splitPane.getLeftComponent() != null &&
552 splitPane.getRightComponent() != null) {
553 if (orientation == JSplitPane.HORIZONTAL_SPLIT) {
554 dragger = new DragController(e);
555 }
556 else {
557 dragger = new VerticalDragController(e);
558 }
559 if (!dragger.isValid()) {
560 dragger = null;
561 }
562 else {
563 prepareForDragging();
564 dragger.continueDrag(e);
565 }
566 }
567 e.consume();
568 }
569 }
570
571
572 /**
573 * If dragger is not null it is messaged with completeDrag.
574 */
575 public void mouseReleased(MouseEvent e) {
576 if (dragger != null) {
577 if (e.getSource() == splitPane) {
578 dragger.completeDrag(e.getX(), e.getY());
579 }
580 else if (e.getSource() == BasicSplitPaneDivider.this) {
581 Point ourLoc = getLocation();
582
583 dragger.completeDrag(e.getX() + ourLoc.x,
584 e.getY() + ourLoc.y);
585 }
586 else if (e.getSource() == hiddenDivider) {
587 Point hDividerLoc = hiddenDivider.getLocation();
588 int ourX = e.getX() + hDividerLoc.x;
589 int ourY = e.getY() + hDividerLoc.y;
590
591 dragger.completeDrag(ourX, ourY);
592 }
593 dragger = null;
594 e.consume();
595 }
596 }
597
598
599 //
600 // MouseMotionListener
601 //
602
603 /**
604 * If dragger is not null it is messaged with continueDrag.
605 */
606 public void mouseDragged(MouseEvent e) {
607 if (dragger != null) {
608 if (e.getSource() == splitPane) {
609 dragger.continueDrag(e.getX(), e.getY());
610 }
611 else if (e.getSource() == BasicSplitPaneDivider.this) {
612 Point ourLoc = getLocation();
613
614 dragger.continueDrag(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.continueDrag(ourX, ourY);
623 }
624 e.consume();
625 }
626 }
627
628
629 /**
630 * Resets the cursor based on the orientation.
631 */
632 public void mouseMoved(MouseEvent e) {
633 }
634
635 /**
636 * Invoked when the mouse enters a component.
637 *
638 * @param e MouseEvent describing the details of the enter event.
639 * @since 1.5
640 */
641 public void mouseEntered(MouseEvent e) {
642 if (e.getSource() == BasicSplitPaneDivider.this) {
643 setMouseOver(true);
644 }
645 }
646
647 /**
648 * Invoked when the mouse exits a component.
649 *
650 * @param e MouseEvent describing the details of the exit event.
651 * @since 1.5
652 */
653 public void mouseExited(MouseEvent e) {
654 if (e.getSource() == BasicSplitPaneDivider.this) {
655 setMouseOver(false);
656 }
657 }
658 }
659
660
661 /**
662 * Handles the events during a dragging session for a
663 * HORIZONTAL_SPLIT oriented split pane. This continually
664 * messages <code>dragDividerTo</code> and then when done messages
665 * <code>finishDraggingTo</code>. When an instance is created it should be
666 * messaged with <code>isValid</code> to insure that dragging can happen
667 * (dragging won't be allowed if the two views can not be resized).
668 * <p>
669 * <strong>Warning:</strong>
670 * Serialized objects of this class will not be compatible with
671 * future Swing releases. The current serialization support is
672 * appropriate for short term storage or RMI between applications running
673 * the same version of Swing. As of 1.4, support for long term storage
674 * of all JavaBeans<sup><font size="-2">TM</font></sup>
675 * has been added to the <code>java.beans</code> package.
676 * Please see {@link java.beans.XMLEncoder}.
677 */
678 protected class DragController
679 {
680 /**
681 * Initial location of the divider.
682 */
683 int initialX;
684
685 /**
686 * Maximum and minimum positions to drag to.
687 */
688 int maxX, minX;
689
690 /**
691 * Initial location the mouse down happened at.
692 */
693 int offset;
694
695
696 protected DragController(MouseEvent e) {
697 JSplitPane splitPane = splitPaneUI.getSplitPane();
698 Component leftC = splitPane.getLeftComponent();
699 Component rightC = splitPane.getRightComponent();
700
701 initialX = getLocation().x;
702 if (e.getSource() == BasicSplitPaneDivider.this) {
703 offset = e.getX();
704 }
705 else { // splitPane
706 offset = e.getX() - initialX;
707 }
708 if (leftC == null || rightC == null || offset < -1 ||
709 offset >= getSize().width) {
710 // Don't allow dragging.
711 maxX = -1;
712 }
713 else {
714 Insets insets = splitPane.getInsets();
715
716 if (leftC.isVisible()) {
717 minX = leftC.getMinimumSize().width;
718 if (insets != null) {
719 minX += insets.left;
720 }
721 }
722 else {
723 minX = 0;
724 }
725 if (rightC.isVisible()) {
726 int right = (insets != null) ? insets.right : 0;
727 maxX = Math.max(0, splitPane.getSize().width -
728 (getSize().width + right) -
729 rightC.getMinimumSize().width);
730 }
731 else {
732 int right = (insets != null) ? insets.right : 0;
733 maxX = Math.max(0, splitPane.getSize().width -
734 (getSize().width + right));
735 }
736 if (maxX < minX) minX = maxX = 0;
737 }
738 }
739
740
741 /**
742 * Returns true if the dragging session is valid.
743 */
744 protected boolean isValid() {
745 return (maxX > 0);
746 }
747
748
749 /**
750 * Returns the new position to put the divider at based on
751 * the passed in MouseEvent.
752 */
753 protected int positionForMouseEvent(MouseEvent e) {
754 int newX = (e.getSource() == BasicSplitPaneDivider.this) ?
755 (e.getX() + getLocation().x) : e.getX();
756
757 newX = Math.min(maxX, Math.max(minX, newX - offset));
758 return newX;
759 }
760
761
762 /**
763 * Returns the x argument, since this is used for horizontal
764 * splits.
765 */
766 protected int getNeededLocation(int x, int y) {
767 int newX;
768
769 newX = Math.min(maxX, Math.max(minX, x - offset));
770 return newX;
771 }
772
773
774 protected void continueDrag(int newX, int newY) {
775 dragDividerTo(getNeededLocation(newX, newY));
776 }
777
778
779 /**
780 * Messages dragDividerTo with the new location for the mouse
781 * event.
782 */
783 protected void continueDrag(MouseEvent e) {
784 dragDividerTo(positionForMouseEvent(e));
785 }
786
787
788 protected void completeDrag(int x, int y) {
789 finishDraggingTo(getNeededLocation(x, y));
790 }
791
792
793 /**
794 * Messages finishDraggingTo with the new location for the mouse
795 * event.
796 */
797 protected void completeDrag(MouseEvent e) {
798 finishDraggingTo(positionForMouseEvent(e));
799 }
800 } // End of BasicSplitPaneDivider.DragController
801
802
803 /**
804 * Handles the events during a dragging session for a
805 * VERTICAL_SPLIT oriented split pane. This continually
806 * messages <code>dragDividerTo</code> and then when done messages
807 * <code>finishDraggingTo</code>. When an instance is created it should be
808 * messaged with <code>isValid</code> to insure that dragging can happen
809 * (dragging won't be allowed if the two views can not be resized).
810 */
811 protected class VerticalDragController extends DragController
812 {
813 /* DragControllers ivars are now in terms of y, not x. */
814 protected VerticalDragController(MouseEvent e) {
815 super(e);
816 JSplitPane splitPane = splitPaneUI.getSplitPane();
817 Component leftC = splitPane.getLeftComponent();
818 Component rightC = splitPane.getRightComponent();
819
820 initialX = getLocation().y;
821 if (e.getSource() == BasicSplitPaneDivider.this) {
822 offset = e.getY();
823 }
824 else {
825 offset = e.getY() - initialX;
826 }
827 if (leftC == null || rightC == null || offset < -1 ||
828 offset > getSize().height) {
829 // Don't allow dragging.
830 maxX = -1;
831 }
832 else {
833 Insets insets = splitPane.getInsets();
834
835 if (leftC.isVisible()) {
836 minX = leftC.getMinimumSize().height;
837 if (insets != null) {
838 minX += insets.top;
839 }
840 }
841 else {
842 minX = 0;
843 }
844 if (rightC.isVisible()) {
845 int bottom = (insets != null) ? insets.bottom : 0;
846
847 maxX = Math.max(0, splitPane.getSize().height -
848 (getSize().height + bottom) -
849 rightC.getMinimumSize().height);
850 }
851 else {
852 int bottom = (insets != null) ? insets.bottom : 0;
853
854 maxX = Math.max(0, splitPane.getSize().height -
855 (getSize().height + bottom));
856 }
857 if (maxX < minX) minX = maxX = 0;
858 }
859 }
860
861
862 /**
863 * Returns the y argument, since this is used for vertical
864 * splits.
865 */
866 protected int getNeededLocation(int x, int y) {
867 int newY;
868
869 newY = Math.min(maxX, Math.max(minX, y - offset));
870 return newY;
871 }
872
873
874 /**
875 * Returns the new position to put the divider at based on
876 * the passed in MouseEvent.
877 */
878 protected int positionForMouseEvent(MouseEvent e) {
879 int newY = (e.getSource() == BasicSplitPaneDivider.this) ?
880 (e.getY() + getLocation().y) : e.getY();
881
882
883 newY = Math.min(maxX, Math.max(minX, newY - offset));
884 return newY;
885 }
886 } // End of BasicSplitPaneDividier.VerticalDragController
887
888
889 /**
890 * Used to layout a <code>BasicSplitPaneDivider</code>.
891 * Layout for the divider
892 * involves appropriately moving the left/right buttons around.
893 * <p>
894 */
895 protected class DividerLayout implements LayoutManager
896 {
897 public void layoutContainer(Container c) {
898 if (leftButton != null && rightButton != null &&
899 c == BasicSplitPaneDivider.this) {
900 if (splitPane.isOneTouchExpandable()) {
901 Insets insets = getInsets();
902
903 if (orientation == JSplitPane.VERTICAL_SPLIT) {
904 int extraX = (insets != null) ? insets.left : 0;
905 int blockSize = getHeight();
906
907 if (insets != null) {
908 blockSize -= (insets.top + insets.bottom);
909 blockSize = Math.max(blockSize, 0);
910 }
911 blockSize = Math.min(blockSize, oneTouchSize);
912
913 int y = (c.getSize().height - blockSize) / 2;
914
915 if (!centerOneTouchButtons) {
916 y = (insets != null) ? insets.top : 0;
917 extraX = 0;
918 }
919 leftButton.setBounds(extraX + oneTouchOffset, y,
920 blockSize * 2, blockSize);
921 rightButton.setBounds(extraX + oneTouchOffset +
922 oneTouchSize * 2, y,
923 blockSize * 2, blockSize);
924 }
925 else {
926 int extraY = (insets != null) ? insets.top : 0;
927 int blockSize = getWidth();
928
929 if (insets != null) {
930 blockSize -= (insets.left + insets.right);
931 blockSize = Math.max(blockSize, 0);
932 }
933 blockSize = Math.min(blockSize, oneTouchSize);
934
935 int x = (c.getSize().width - blockSize) / 2;
936
937 if (!centerOneTouchButtons) {
938 x = (insets != null) ? insets.left : 0;
939 extraY = 0;
940 }
941
942 leftButton.setBounds(x, extraY + oneTouchOffset,
943 blockSize, blockSize * 2);
944 rightButton.setBounds(x, extraY + oneTouchOffset +
945 oneTouchSize * 2, blockSize,
946 blockSize * 2);
947 }
948 }
949 else {
950 leftButton.setBounds(-5, -5, 1, 1);
951 rightButton.setBounds(-5, -5, 1, 1);
952 }
953 }
954 }
955
956
957 public Dimension minimumLayoutSize(Container c) {
958 // NOTE: This isn't really used, refer to
959 // BasicSplitPaneDivider.getPreferredSize for the reason.
960 // I leave it in hopes of having this used at some point.
961 if (c != BasicSplitPaneDivider.this || splitPane == null) {
962 return new Dimension(0,0);
963 }
964 Dimension buttonMinSize = null;
965
966 if (splitPane.isOneTouchExpandable() && leftButton != null) {
967 buttonMinSize = leftButton.getMinimumSize();
968 }
969
970 Insets insets = getInsets();
971 int width = getDividerSize();
972 int height = width;
973
974 if (orientation == JSplitPane.VERTICAL_SPLIT) {
975 if (buttonMinSize != null) {
976 int size = buttonMinSize.height;
977 if (insets != null) {
978 size += insets.top + insets.bottom;
979 }
980 height = Math.max(height, size);
981 }
982 width = 1;
983 }
984 else {
985 if (buttonMinSize != null) {
986 int size = buttonMinSize.width;
987 if (insets != null) {
988 size += insets.left + insets.right;
989 }
990 width = Math.max(width, size);
991 }
992 height = 1;
993 }
994 return new Dimension(width, height);
995 }
996
997
998 public Dimension preferredLayoutSize(Container c) {
999 return minimumLayoutSize(c);
1000 }
1001
1002
1003 public void removeLayoutComponent(Component c) {}
1004
1005 public void addLayoutComponent(String string, Component c) {}
1006 } // End of class BasicSplitPaneDivider.DividerLayout
1007
1008
1009 /**
1010 * Listeners installed on the one touch expandable buttons.
1011 */
1012 private class OneTouchActionHandler implements ActionListener {
1013 /** True indicates the resize should go the minimum (top or left)
1014 * vs false which indicates the resize should go to the maximum.
1015 */
1016 private boolean toMinimum;
1017
1018 OneTouchActionHandler(boolean toMinimum) {
1019 this.toMinimum = toMinimum;
1020 }
1021
1022 public void actionPerformed(ActionEvent e) {
1023 Insets insets = splitPane.getInsets();
1024 int lastLoc = splitPane.getLastDividerLocation();
1025 int currentLoc = splitPaneUI.getDividerLocation(splitPane);
1026 int newLoc;
1027
1028 // We use the location from the UI directly, as the location the
1029 // JSplitPane itself maintains is not necessarly correct.
1030 if (toMinimum) {
1031 if (orientation == JSplitPane.VERTICAL_SPLIT) {
1032 if (currentLoc >= (splitPane.getHeight() -
1033 insets.bottom - getHeight())) {
1034 int maxLoc = splitPane.getMaximumDividerLocation();
1035 newLoc = Math.min(lastLoc, maxLoc);
1036 splitPaneUI.setKeepHidden(false);
1037 }
1038 else {
1039 newLoc = insets.top;
1040 splitPaneUI.setKeepHidden(true);
1041 }
1042 }
1043 else {
1044 if (currentLoc >= (splitPane.getWidth() -
1045 insets.right - getWidth())) {
1046 int maxLoc = splitPane.getMaximumDividerLocation();
1047 newLoc = Math.min(lastLoc, maxLoc);
1048 splitPaneUI.setKeepHidden(false);
1049 }
1050 else {
1051 newLoc = insets.left;
1052 splitPaneUI.setKeepHidden(true);
1053 }
1054 }
1055 }
1056 else {
1057 if (orientation == JSplitPane.VERTICAL_SPLIT) {
1058 if (currentLoc == insets.top) {
1059 int maxLoc = splitPane.getMaximumDividerLocation();
1060 newLoc = Math.min(lastLoc, maxLoc);
1061 splitPaneUI.setKeepHidden(false);
1062 }
1063 else {
1064 newLoc = splitPane.getHeight() - getHeight() -
1065 insets.top;
1066 splitPaneUI.setKeepHidden(true);
1067 }
1068 }
1069 else {
1070 if (currentLoc == insets.left) {
1071 int maxLoc = splitPane.getMaximumDividerLocation();
1072 newLoc = Math.min(lastLoc, maxLoc);
1073 splitPaneUI.setKeepHidden(false);
1074 }
1075 else {
1076 newLoc = splitPane.getWidth() - getWidth() -
1077 insets.left;
1078 splitPaneUI.setKeepHidden(true);
1079 }
1080 }
1081 }
1082 if (currentLoc != newLoc) {
1083 splitPane.setDividerLocation(newLoc);
1084 // We do this in case the dividers notion of the location
1085 // differs from the real location.
1086 splitPane.setLastDividerLocation(currentLoc);
1087 }
1088 }
1089 } // End of class BasicSplitPaneDivider.LeftActionListener
1090 }