001 /* BasicSplitPaneUI.java --
002 Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.Canvas;
042 import java.awt.Color;
043 import java.awt.Component;
044 import java.awt.Container;
045 import java.awt.Dimension;
046 import java.awt.Graphics;
047 import java.awt.Insets;
048 import java.awt.LayoutManager2;
049 import java.awt.Point;
050 import java.awt.event.ActionEvent;
051 import java.awt.event.ActionListener;
052 import java.awt.event.FocusAdapter;
053 import java.awt.event.FocusEvent;
054 import java.awt.event.FocusListener;
055 import java.beans.PropertyChangeEvent;
056 import java.beans.PropertyChangeListener;
057
058 import javax.swing.AbstractAction;
059 import javax.swing.ActionMap;
060 import javax.swing.InputMap;
061 import javax.swing.JComponent;
062 import javax.swing.JSlider;
063 import javax.swing.JSplitPane;
064 import javax.swing.KeyStroke;
065 import javax.swing.LookAndFeel;
066 import javax.swing.SwingConstants;
067 import javax.swing.SwingUtilities;
068 import javax.swing.UIManager;
069 import javax.swing.plaf.ActionMapUIResource;
070 import javax.swing.plaf.ComponentUI;
071 import javax.swing.plaf.SplitPaneUI;
072 import javax.swing.plaf.UIResource;
073
074 /**
075 * This is the Basic Look and Feel implementation of the SplitPaneUI class.
076 */
077 public class BasicSplitPaneUI extends SplitPaneUI
078 {
079 /**
080 * This Layout Manager controls the position and size of the components when
081 * the JSplitPane's orientation is HORIZONTAL_SPLIT.
082 *
083 * @specnote Apparently this class was intended to be protected,
084 * but was made public by a compiler bug and is now
085 * public for compatibility.
086 */
087 public class BasicHorizontalLayoutManager implements LayoutManager2
088 {
089 // 3 components at a time.
090 // LEFT/TOP = 0
091 // RIGHT/BOTTOM = 1
092 // DIVIDER = 2
093
094 /**
095 * This array contains the components in the JSplitPane. The left/top
096 * component is at index 0, the right/bottom is at 1, and the divider is
097 * at 2.
098 */
099 protected Component[] components = new Component[3];
100
101 // These are the _current_ widths of the associated component.
102
103 /**
104 * This array contains the current width (for HORIZONTAL_SPLIT) or height
105 * (for VERTICAL_SPLIT) of the components. The indices are the same as
106 * for components.
107 */
108 protected int[] sizes = new int[3];
109
110 /**
111 * This is used to determine if we are vertical or horizontal layout.
112 * In the JDK, the BasicVerticalLayoutManager seems to have no more
113 * methods implemented (as of JDK5), so we keep this state here.
114 */
115 private int axis;
116
117 /**
118 * Creates a new instance. This is package private because the reference
119 * implementation has no public constructor either. Still, we need to
120 * call it from BasicVerticalLayoutManager.
121 */
122 BasicHorizontalLayoutManager()
123 {
124 this(SwingConstants.HORIZONTAL);
125 }
126
127 /**
128 * Creates a new instance for a specified axis. This is provided for
129 * compatibility, since the BasicVerticalLayoutManager seems to have
130 * no more implementation in the RI, according to the specs. So
131 * we handle all the axis specific stuff here.
132 *
133 * @param a the axis, either SwingConstants#HORIZONTAL,
134 * or SwingConstants#VERTICAL
135 */
136 BasicHorizontalLayoutManager(int a)
137 {
138 axis = a;
139 }
140
141 /**
142 * This method adds the component given to the JSplitPane. The position of
143 * the component is given by the constraints object.
144 *
145 * @param comp The Component to add.
146 * @param constraints The constraints that bind the object.
147 */
148 public void addLayoutComponent(Component comp, Object constraints)
149 {
150 addLayoutComponent((String) constraints, comp);
151 }
152
153 /**
154 * This method is called to add a Component to the JSplitPane. The
155 * placement string determines where the Component will be placed. The
156 * string should be one of LEFT, RIGHT, TOP, BOTTOM or null (signals that
157 * the component is the divider).
158 *
159 * @param place The placement of the Component.
160 * @param component The Component to add.
161 *
162 * @throws IllegalArgumentException DOCUMENT ME!
163 */
164 public void addLayoutComponent(String place, Component component)
165 {
166 int i = 0;
167 if (place == null)
168 i = 2;
169 else if (place.equals(JSplitPane.TOP) || place.equals(JSplitPane.LEFT))
170 i = 0;
171 else if (place.equals(JSplitPane.BOTTOM)
172 || place.equals(JSplitPane.RIGHT))
173 i = 1;
174 else
175 throw new IllegalArgumentException("Illegal placement in JSplitPane");
176 components[i] = component;
177 resetSizeAt(i);
178 splitPane.revalidate();
179 splitPane.repaint();
180 }
181
182 /**
183 * This method returns the width of the JSplitPane minus the insets.
184 *
185 * @param containerSize The Dimensions of the JSplitPane.
186 * @param insets The Insets of the JSplitPane.
187 *
188 * @return The width of the JSplitPane minus the insets.
189 */
190 protected int getAvailableSize(Dimension containerSize, Insets insets)
191 {
192 int size;
193 if (axis == SwingConstants.HORIZONTAL)
194 size = containerSize.width - insets.left - insets.right;
195 else
196 size = containerSize.height - insets.top - insets.bottom;
197 return size;
198 }
199
200 /**
201 * This method returns the given insets left value. If the given inset is
202 * null, then 0 is returned.
203 *
204 * @param insets The Insets to use with the JSplitPane.
205 *
206 * @return The inset's left value.
207 */
208 protected int getInitialLocation(Insets insets)
209 {
210 int loc = 0;
211 if (insets != null)
212 {
213 if (axis == SwingConstants.HORIZONTAL)
214 loc = insets.left;
215 else
216 loc = insets.top;
217 }
218 return loc;
219 }
220
221 /**
222 * This specifies how a component is aligned with respect to other
223 * components in the x fdirection.
224 *
225 * @param target The container.
226 *
227 * @return The component's alignment.
228 */
229 public float getLayoutAlignmentX(Container target)
230 {
231 return 0.0f;
232 }
233
234 /**
235 * This specifies how a component is aligned with respect to other
236 * components in the y direction.
237 *
238 * @param target The container.
239 *
240 * @return The component's alignment.
241 */
242 public float getLayoutAlignmentY(Container target)
243 {
244 return 0.0f;
245 }
246
247 /**
248 * This method returns the preferred width of the component.
249 *
250 * @param c The component to measure.
251 *
252 * @return The preferred width of the component.
253 */
254 protected int getPreferredSizeOfComponent(Component c)
255 {
256 int size = 0;
257 Dimension dims = c.getPreferredSize();
258 if (axis == SwingConstants.HORIZONTAL)
259 {
260 if (dims != null)
261 size = dims.width;
262 }
263 else
264 {
265 if (dims != null)
266 size = dims.height;
267 }
268 return size;
269 }
270
271 /**
272 * This method returns the current width of the component.
273 *
274 * @param c The component to measure.
275 *
276 * @return The width of the component.
277 */
278 protected int getSizeOfComponent(Component c)
279 {
280 int size;
281 if (axis == SwingConstants.HORIZONTAL)
282 size = c.getHeight();
283 else
284 size = c.getWidth();
285 return size;
286 }
287
288 /**
289 * This method returns the sizes array.
290 *
291 * @return The sizes array.
292 */
293 protected int[] getSizes()
294 {
295 return sizes;
296 }
297
298 /**
299 * This method invalidates the layout. It does nothing.
300 *
301 * @param c The container to invalidate.
302 */
303 public void invalidateLayout(Container c)
304 {
305 // DO NOTHING
306 }
307
308 /**
309 * This method lays out the components in the container.
310 *
311 * @param container The container to lay out.
312 */
313 public void layoutContainer(Container container)
314 {
315 if (container instanceof JSplitPane)
316 {
317 JSplitPane split = (JSplitPane) container;
318 distributeExtraSpace();
319 Insets insets = split.getInsets();
320 Dimension dims = split.getSize();
321 int loc = getInitialLocation(insets);
322 int available = getAvailableSize(dims, insets);
323 sizes[0] = split.getDividerLocation();
324 sizes[1] = available - sizes[0] - sizes[2];
325
326 // According to a Mauve test we only honour the minimum
327 // size of the components, when the dividerLocation hasn't
328 // been excplicitly set.
329 if (! dividerLocationSet)
330 {
331 sizes[0] = Math.max(sizes[0], minimumSizeOfComponent(0));
332 sizes[1] = Math.max(sizes[1], minimumSizeOfComponent(1));
333 }
334 // The size of the divider won't change.
335
336 // Layout component#1.
337 setComponentToSize(components[0], sizes[0], loc, insets, dims);
338 // Layout divider.
339 loc += sizes[0];
340 setComponentToSize(components[2], sizes[2], loc, insets, dims);
341 // Layout component#2.
342 loc += sizes[2];
343 setComponentToSize(components[1], sizes[1], loc, insets, dims);
344 }
345 }
346
347 /**
348 * This method returns the maximum size for the container given the
349 * components. It returns a new Dimension object that has width and
350 * height equal to Integer.MAX_VALUE.
351 *
352 * @param target The container to measure.
353 *
354 * @return The maximum size.
355 */
356 public Dimension maximumLayoutSize(Container target)
357 {
358 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
359 }
360
361 /**
362 * This method returns the container's minimum size. The minimum width is
363 * the sum of all the component's minimum widths. The minimum height is
364 * the maximum of all the components' minimum heights.
365 *
366 * @param target The container to measure.
367 *
368 * @return The minimum size.
369 */
370 public Dimension minimumLayoutSize(Container target)
371 {
372 Dimension dim = new Dimension();
373 if (target instanceof JSplitPane)
374 {
375 int primary = 0;
376 int secondary = 0;
377 for (int i = 0; i < components.length; i++)
378 {
379 if (components[i] != null)
380 {
381 Dimension dims = components[i].getMinimumSize();
382 primary += axis == SwingConstants.HORIZONTAL ? dims.width
383 : dims.height;
384 int sec = axis == SwingConstants.HORIZONTAL ? dims.height
385 : dims.width;
386 secondary = Math.max(sec, secondary);
387 }
388 }
389 int width = axis == SwingConstants.HORIZONTAL ? primary : secondary;
390 int height = axis == SwingConstants.VERTICAL ? secondary : primary;
391
392 Insets i = splitPane.getInsets();
393 dim.setSize(width + i.left + i.right, height + i.top + i.bottom);
394 }
395 return dim;
396 }
397
398 /**
399 * This method returns the container's preferred size. The preferred width
400 * is the sum of all the component's preferred widths. The preferred
401 * height is the maximum of all the components' preferred heights.
402 *
403 * @param target The container to measure.
404 *
405 * @return The preferred size.
406 */
407 public Dimension preferredLayoutSize(Container target)
408 {
409 Dimension dim = new Dimension();
410 if (target instanceof JSplitPane)
411 {
412 int primary = 0;
413 int secondary = 0;
414 for (int i = 0; i < components.length; i++)
415 {
416 if (components[i] != null)
417 {
418 Dimension dims = components[i].getPreferredSize();
419 primary += axis == SwingConstants.HORIZONTAL ? dims.width
420 : dims.height;
421 int sec = axis == SwingConstants.HORIZONTAL ? dims.height
422 : dims.width;
423 secondary = Math.max(sec, secondary);
424 }
425 }
426 int width = axis == SwingConstants.HORIZONTAL ? primary : secondary;
427 int height = axis == SwingConstants.VERTICAL ? secondary : primary;
428
429 Insets i = splitPane.getInsets();
430 dim.setSize(width + i.left + i.right, height + i.top + i.bottom);
431 }
432 return dim;
433 }
434
435 /**
436 * This method removes the component from the layout.
437 *
438 * @param component The component to remove from the layout.
439 */
440 public void removeLayoutComponent(Component component)
441 {
442 for (int i = 0; i < components.length; i++)
443 {
444 if (component == components[i])
445 {
446 components[i] = null;
447 sizes[i] = 0;
448 }
449 }
450 }
451
452 /**
453 * This method resets the size of Component to the preferred size.
454 *
455 * @param index The index of the component to reset.
456 */
457 protected void resetSizeAt(int index)
458 {
459 if (components[index] != null)
460 sizes[index] = getPreferredSizeOfComponent(components[index]);
461 }
462
463 /**
464 * This method resets the sizes of all the components.
465 */
466 public void resetToPreferredSizes()
467 {
468 for (int i = 0; i < components.length; i++)
469 resetSizeAt(i);
470 }
471
472 /**
473 * This methods sets the bounds of the given component. The width is the
474 * size. The height is the container size minus the top and bottom
475 * inset. The x coordinate is the location given. The y coordinate is
476 * the top inset.
477 *
478 * @param c The component to set.
479 * @param size The width of the component.
480 * @param location The x coordinate.
481 * @param insets The insets to use.
482 * @param containerSize The height of the container.
483 */
484 protected void setComponentToSize(Component c, int size, int location,
485 Insets insets, Dimension containerSize)
486 {
487 if (insets != null)
488 {
489 if (axis == SwingConstants.HORIZONTAL)
490 c.setBounds(location, insets.top, size,
491 containerSize.height - insets.top - insets.bottom);
492 else
493 c.setBounds(insets.left, location,
494 containerSize.width - insets.left - insets.right,
495 size);
496 }
497 else
498 {
499 if (axis == SwingConstants.HORIZONTAL)
500 c.setBounds(location, 0, size, containerSize.height);
501 else
502 c.setBounds(0, location, containerSize.width, size);
503 }
504 }
505
506 /**
507 * This method stores the given int array as the new sizes array.
508 *
509 * @param newSizes The array to use as sizes.
510 */
511 protected void setSizes(int[] newSizes)
512 {
513 sizes = newSizes;
514 }
515
516 /**
517 * This method determines the size of each component. It should be called
518 * when a new Layout Manager is created for an existing JSplitPane.
519 */
520 protected void updateComponents()
521 {
522 Component left = splitPane.getLeftComponent();
523 Component right = splitPane.getRightComponent();
524
525 if (left != null)
526 {
527 components[0] = left;
528 resetSizeAt(0);
529 }
530 if (right != null)
531 {
532 components[1] = right;
533 resetSizeAt(1);
534 }
535 components[2] = divider;
536 }
537
538 /**
539 * This method resizes the left and right components to fit inside the
540 * JSplitPane when there is extra space.
541 */
542 void distributeExtraSpace()
543 {
544 // FIXME: This needs to be reimplemented correctly.
545 }
546
547 /**
548 * This method returns the minimum width of the component at the given
549 * index.
550 *
551 * @param index The index to check.
552 *
553 * @return The minimum width.
554 */
555 int minimumSizeOfComponent(int index)
556 {
557 Dimension dims = components[index].getMinimumSize();
558 int size = 0;
559 if (dims != null)
560 if (axis == SwingConstants.HORIZONTAL)
561 size = dims.width;
562 else
563 size = dims.height;
564 return size;
565 }
566 } //end BasicHorizontalLayoutManager
567
568 /**
569 * This class is the Layout Manager for the JSplitPane when the orientation
570 * is VERTICAL_SPLIT.
571 *
572 * @specnote Apparently this class was intended to be protected,
573 * but was made public by a compiler bug and is now
574 * public for compatibility.
575 */
576 public class BasicVerticalLayoutManager
577 extends BasicHorizontalLayoutManager
578 {
579 /**
580 * Creates a new instance.
581 */
582 public BasicVerticalLayoutManager()
583 {
584 super(SwingConstants.VERTICAL);
585 }
586 }
587
588 /**
589 * This class handles FocusEvents from the JComponent.
590 *
591 * @specnote Apparently this class was intended to be protected,
592 * but was made public by a compiler bug and is now
593 * public for compatibility.
594 */
595 public class FocusHandler extends FocusAdapter
596 {
597 /**
598 * This method is called when the JSplitPane gains focus.
599 *
600 * @param ev The FocusEvent.
601 */
602 public void focusGained(FocusEvent ev)
603 {
604 // repaint the divider because its background color may change due to
605 // the focus state...
606 divider.repaint();
607 }
608
609 /**
610 * This method is called when the JSplitPane loses focus.
611 *
612 * @param ev The FocusEvent.
613 */
614 public void focusLost(FocusEvent ev)
615 {
616 // repaint the divider because its background color may change due to
617 // the focus state...
618 divider.repaint();
619 }
620 }
621
622 /**
623 * This is a deprecated class. It is supposed to be used for handling down
624 * and right key presses.
625 *
626 * @specnote Apparently this class was intended to be protected,
627 * but was made public by a compiler bug and is now
628 * public for compatibility.
629 */
630 public class KeyboardDownRightHandler implements ActionListener
631 {
632 /**
633 * This method is called when the down or right keys are pressed.
634 *
635 * @param ev The ActionEvent
636 */
637 public void actionPerformed(ActionEvent ev)
638 {
639 // FIXME: implement.
640 }
641 }
642
643 /**
644 * This is a deprecated class. It is supposed to be used for handling end
645 * key presses.
646 *
647 * @specnote Apparently this class was intended to be protected,
648 * but was made public by a compiler bug and is now
649 * public for compatibility.
650 */
651 public class KeyboardEndHandler implements ActionListener
652 {
653 /**
654 * This method is called when the end key is pressed.
655 *
656 * @param ev The ActionEvent.
657 */
658 public void actionPerformed(ActionEvent ev)
659 {
660 // FIXME: implement.
661 }
662 }
663
664 /**
665 * This is a deprecated class. It is supposed to be used for handling home
666 * key presses.
667 *
668 * @specnote Apparently this class was intended to be protected,
669 * but was made public by a compiler bug and is now
670 * public for compatibility.
671 */
672 public class KeyboardHomeHandler implements ActionListener
673 {
674 /**
675 * This method is called when the home key is pressed.
676 *
677 * @param ev The ActionEvent.
678 */
679 public void actionPerformed(ActionEvent ev)
680 {
681 // FIXME: implement.
682 }
683 }
684
685 /**
686 * This is a deprecated class. It is supposed to be used for handling resize
687 * toggles.
688 *
689 * @specnote Apparently this class was intended to be protected,
690 * but was made public by a compiler bug and is now
691 * public for compatibility.
692 */
693 public class KeyboardResizeToggleHandler implements ActionListener
694 {
695 /**
696 * This method is called when a resize is toggled.
697 *
698 * @param ev The ActionEvent.
699 */
700 public void actionPerformed(ActionEvent ev)
701 {
702 // FIXME: implement.
703 }
704 }
705
706 /**
707 * This is a deprecated class. It is supposed to be used for handler up and
708 * left key presses.
709 *
710 * @specnote Apparently this class was intended to be protected,
711 * but was made public by a compiler bug and is now
712 * public for compatibility.
713 */
714 public class KeyboardUpLeftHandler implements ActionListener
715 {
716 /**
717 * This method is called when the left or up keys are pressed.
718 *
719 * @param ev The ActionEvent.
720 */
721 public void actionPerformed(ActionEvent ev)
722 {
723 // FIXME: implement.
724 }
725 }
726
727 /**
728 * This helper class handles PropertyChangeEvents from the JSplitPane. When
729 * a property changes, this will update the UI accordingly.
730 *
731 * @specnote Apparently this class was intended to be protected,
732 * but was made public by a compiler bug and is now
733 * public for compatibility.
734 */
735 public class PropertyHandler implements PropertyChangeListener
736 {
737 /**
738 * This method is called whenever one of the JSplitPane's properties
739 * change.
740 *
741 * @param e DOCUMENT ME!
742 */
743 public void propertyChange(PropertyChangeEvent e)
744 {
745 if (e.getPropertyName().equals(JSplitPane.DIVIDER_SIZE_PROPERTY))
746 {
747 int newSize = splitPane.getDividerSize();
748 int[] tmpSizes = layoutManager.getSizes();
749 dividerSize = tmpSizes[2];
750 int newSpace = newSize - tmpSizes[2];
751 tmpSizes[2] = newSize;
752
753 tmpSizes[0] += newSpace / 2;
754 tmpSizes[1] += newSpace / 2;
755
756 layoutManager.setSizes(tmpSizes);
757 }
758 else if (e.getPropertyName().equals(JSplitPane.ORIENTATION_PROPERTY))
759 {
760 int max = layoutManager.getAvailableSize(splitPane.getSize(),
761 splitPane.getInsets());
762 int dividerLoc = getDividerLocation(splitPane);
763 double prop = ((double) dividerLoc) / max;
764
765 resetLayoutManager();
766 if (prop <= 1 && prop >= 0)
767 splitPane.setDividerLocation(prop);
768 }
769 // Don't have to deal with continuous_layout - only
770 // necessary in dragging modes (and it's checked
771 // every time you drag there)
772 // Don't have to deal with resize_weight (as there
773 // will be no extra space associated with this
774 // event - the changes to the weighting will
775 // be taken into account the next time the
776 // sizes change.)
777 // Don't have to deal with divider_location
778 // The method in JSplitPane calls our setDividerLocation
779 // so we'll know about those anyway.
780 // Don't have to deal with last_divider_location
781 // Although I'm not sure why, it doesn't seem to
782 // have any effect on Sun's JSplitPane.
783 // one_touch_expandable changes are dealt with
784 // by our divider.
785 }
786 }
787
788 /** The location of the divider when dragging began. */
789 protected int beginDragDividerLocation;
790
791 /** The size of the divider while dragging. */
792 protected int dividerSize;
793
794 /** The location where the last drag location ended. */
795 transient int lastDragLocation = -1;
796
797 /** The distance the divider is moved when moved by keyboard actions. */
798 // Sun defines this as 3
799 protected static int KEYBOARD_DIVIDER_MOVE_OFFSET = 3;
800
801 /** The divider that divides this JSplitPane. */
802 protected BasicSplitPaneDivider divider;
803
804 /** The listener that listens for PropertyChangeEvents from the JSplitPane. */
805 protected PropertyChangeListener propertyChangeListener;
806
807 /** The JSplitPane's focus handler. */
808 protected FocusListener focusListener;
809
810 /** @deprecated The handler for down and right key presses. */
811 protected ActionListener keyboardDownRightListener;
812
813 /** @deprecated The handler for end key presses. */
814 protected ActionListener keyboardEndListener;
815
816 /** @deprecated The handler for home key presses. */
817 protected ActionListener keyboardHomeListener;
818
819 /** @deprecated The handler for toggling resizes. */
820 protected ActionListener keyboardResizeToggleListener;
821
822 /** @deprecated The handler for up and left key presses. */
823 protected ActionListener keyboardUpLeftListener;
824
825 /** The JSplitPane's current layout manager. */
826 protected BasicHorizontalLayoutManager layoutManager;
827
828 /** @deprecated The divider resize toggle key. */
829 protected KeyStroke dividerResizeToggleKey;
830
831 /** @deprecated The down key. */
832 protected KeyStroke downKey;
833
834 /** @deprecated The end key. */
835 protected KeyStroke endKey;
836
837 /** @deprecated The home key. */
838 protected KeyStroke homeKey;
839
840 /** @deprecated The left key. */
841 protected KeyStroke leftKey;
842
843 /** @deprecated The right key. */
844 protected KeyStroke rightKey;
845
846 /** @deprecated The up key. */
847 protected KeyStroke upKey;
848
849 /** Set to true when dragging heavy weight components. */
850 protected boolean draggingHW;
851
852 /**
853 * The constraints object used when adding the non-continuous divider to the
854 * JSplitPane.
855 */
856 protected static final String NON_CONTINUOUS_DIVIDER
857 = "nonContinuousDivider";
858
859 /** The dark divider used when dragging in non-continuous layout mode. */
860 protected Component nonContinuousLayoutDivider;
861
862 /** The JSplitPane that this UI draws. */
863 protected JSplitPane splitPane;
864
865 /**
866 * True, when setDividerLocation() has been called at least
867 * once on the JSplitPane, false otherwise.
868 *
869 * This is package private to avoid a synthetic accessor method.
870 */
871 boolean dividerLocationSet;
872
873 /**
874 * Creates a new BasicSplitPaneUI object.
875 */
876 public BasicSplitPaneUI()
877 {
878 // Nothing to do here.
879 }
880
881 /**
882 * This method creates a new BasicSplitPaneUI for the given JComponent.
883 *
884 * @param x The JComponent to create a UI for.
885 *
886 * @return A new BasicSplitPaneUI.
887 */
888 public static ComponentUI createUI(JComponent x)
889 {
890 return new BasicSplitPaneUI();
891 }
892
893 /**
894 * This method installs the BasicSplitPaneUI for the given JComponent.
895 *
896 * @param c The JComponent to install the UI for.
897 */
898 public void installUI(JComponent c)
899 {
900 if (c instanceof JSplitPane)
901 {
902 splitPane = (JSplitPane) c;
903 dividerLocationSet = false;
904 installDefaults();
905 installListeners();
906 installKeyboardActions();
907 }
908 }
909
910 /**
911 * This method uninstalls the BasicSplitPaneUI for the given JComponent.
912 *
913 * @param c The JComponent to uninstall the UI for.
914 */
915 public void uninstallUI(JComponent c)
916 {
917 uninstallKeyboardActions();
918 uninstallListeners();
919 uninstallDefaults();
920
921 dividerLocationSet = false;
922 splitPane = null;
923 }
924
925 /**
926 * This method installs the defaults given by the Look and Feel.
927 */
928 protected void installDefaults()
929 {
930 LookAndFeel.installColors(splitPane, "SplitPane.background",
931 "SplitPane.foreground");
932 LookAndFeel.installBorder(splitPane, "SplitPane.border");
933 divider = createDefaultDivider();
934 divider.setBorder(UIManager.getBorder("SplitPaneDivider.border"));
935 resetLayoutManager();
936 nonContinuousLayoutDivider = createDefaultNonContinuousLayoutDivider();
937 splitPane.add(divider, JSplitPane.DIVIDER);
938
939 // There is no need to add the nonContinuousLayoutDivider.
940 dividerSize = UIManager.getInt("SplitPane.dividerSize");
941 splitPane.setDividerSize(dividerSize);
942 divider.setDividerSize(dividerSize);
943 splitPane.setOpaque(true);
944 }
945
946 /**
947 * This method uninstalls the defaults and nulls any objects created during
948 * install.
949 */
950 protected void uninstallDefaults()
951 {
952 layoutManager = null;
953 splitPane.remove(divider);
954 divider = null;
955 nonContinuousLayoutDivider = null;
956
957 if (splitPane.getBackground() instanceof UIResource)
958 splitPane.setBackground(null);
959 if (splitPane.getBorder() instanceof UIResource)
960 splitPane.setBorder(null);
961 }
962
963 /**
964 * This method installs the listeners needed for this UI to function.
965 */
966 protected void installListeners()
967 {
968 propertyChangeListener = createPropertyChangeListener();
969 focusListener = createFocusListener();
970
971 splitPane.addPropertyChangeListener(propertyChangeListener);
972 splitPane.addFocusListener(focusListener);
973 }
974
975 /**
976 * This method uninstalls all listeners registered for the UI.
977 */
978 protected void uninstallListeners()
979 {
980 splitPane.removePropertyChangeListener(propertyChangeListener);
981 splitPane.removeFocusListener(focusListener);
982
983 focusListener = null;
984 propertyChangeListener = null;
985 }
986
987 /**
988 * Returns the input map for the specified condition.
989 *
990 * @param condition the condition.
991 *
992 * @return The input map.
993 */
994 InputMap getInputMap(int condition)
995 {
996 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
997 return (InputMap) UIManager.get("SplitPane.ancestorInputMap");
998 return null;
999 }
1000
1001 /**
1002 * Returns the action map for the {@link JSplitPane}. All sliders share
1003 * a single action map which is created the first time this method is
1004 * called, then stored in the UIDefaults table for subsequent access.
1005 *
1006 * @return The shared action map.
1007 */
1008 ActionMap getActionMap()
1009 {
1010 ActionMap map = (ActionMap) UIManager.get("SplitPane.actionMap");
1011
1012 if (map == null) // first time here
1013 {
1014 map = createActionMap();
1015 if (map != null)
1016 UIManager.put("SplitPane.actionMap", map);
1017 }
1018 return map;
1019 }
1020
1021 /**
1022 * Creates the action map shared by all {@link JSlider} instances.
1023 * This method is called once by {@link #getActionMap()} when it
1024 * finds no action map in the UIDefaults table...after the map is
1025 * created, it gets added to the defaults table so that subsequent
1026 * calls to {@link #getActionMap()} will return the same shared
1027 * instance.
1028 *
1029 * @return The action map.
1030 */
1031 ActionMap createActionMap()
1032 {
1033 ActionMap map = new ActionMapUIResource();
1034 map.put("toggleFocus",
1035 new AbstractAction("toggleFocus") {
1036 public void actionPerformed(ActionEvent event)
1037 {
1038 // FIXME: What to do here?
1039 }
1040 }
1041 );
1042 map.put("startResize",
1043 new AbstractAction("startResize") {
1044 public void actionPerformed(ActionEvent event)
1045 {
1046 splitPane.requestFocus();
1047 }
1048 }
1049 );
1050 map.put("selectMax",
1051 new AbstractAction("selectMax") {
1052 public void actionPerformed(ActionEvent event)
1053 {
1054 splitPane.setDividerLocation(1.0);
1055 }
1056 }
1057 );
1058 map.put("selectMin",
1059 new AbstractAction("selectMin") {
1060 public void actionPerformed(ActionEvent event)
1061 {
1062 splitPane.setDividerLocation(0.0);
1063 }
1064 }
1065 );
1066 map.put("negativeIncrement",
1067 new AbstractAction("negativeIncrement") {
1068 public void actionPerformed(ActionEvent event)
1069 {
1070 int oldLoc = splitPane.getDividerLocation();
1071 int newLoc =
1072 Math.max(oldLoc - KEYBOARD_DIVIDER_MOVE_OFFSET, 0);
1073 splitPane.setDividerLocation(newLoc);
1074 }
1075 }
1076 );
1077 map.put("positiveIncrement",
1078 new AbstractAction("positiveIncrement") {
1079 public void actionPerformed(ActionEvent event)
1080 {
1081 int oldLoc = splitPane.getDividerLocation();
1082 int newLoc =
1083 Math.max(oldLoc + KEYBOARD_DIVIDER_MOVE_OFFSET, 0);
1084 splitPane.setDividerLocation(newLoc);
1085 }
1086 }
1087 );
1088 map.put("focusOutBackward",
1089 new AbstractAction("focusOutBackward") {
1090 public void actionPerformed(ActionEvent event)
1091 {
1092 // FIXME: implement this
1093 }
1094 }
1095 );
1096 map.put("focusOutForward",
1097 new AbstractAction("focusOutForward") {
1098 public void actionPerformed(ActionEvent event)
1099 {
1100 // FIXME: implement this
1101 }
1102 }
1103 );
1104 return map;
1105 }
1106
1107 /**
1108 * Installs any keyboard actions. The list of keys that need to be bound are
1109 * listed in Basic look and feel's defaults.
1110 */
1111 protected void installKeyboardActions()
1112 {
1113 InputMap keyMap = getInputMap(
1114 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1115 SwingUtilities.replaceUIInputMap(splitPane,
1116 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
1117 ActionMap map = getActionMap();
1118 SwingUtilities.replaceUIActionMap(splitPane, map);
1119 }
1120
1121 /**
1122 * This method reverses the work done in installKeyboardActions.
1123 */
1124 protected void uninstallKeyboardActions()
1125 {
1126 SwingUtilities.replaceUIActionMap(splitPane, null);
1127 SwingUtilities.replaceUIInputMap(splitPane,
1128 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1129 }
1130
1131 /**
1132 * This method creates a new PropertyChangeListener.
1133 *
1134 * @return A new PropertyChangeListener.
1135 */
1136 protected PropertyChangeListener createPropertyChangeListener()
1137 {
1138 return new PropertyHandler();
1139 }
1140
1141 /**
1142 * This method creates a new FocusListener.
1143 *
1144 * @return A new FocusListener.
1145 */
1146 protected FocusListener createFocusListener()
1147 {
1148 return new FocusHandler();
1149 }
1150
1151 /**
1152 * This method creates a new ActionListener for up and left key presses.
1153 *
1154 * @return A new ActionListener for up and left keys.
1155 *
1156 * @deprecated 1.3
1157 */
1158 protected ActionListener createKeyboardUpLeftListener()
1159 {
1160 return new KeyboardUpLeftHandler();
1161 }
1162
1163 /**
1164 * This method creates a new ActionListener for down and right key presses.
1165 *
1166 * @return A new ActionListener for down and right keys.
1167 *
1168 * @deprecated 1.3
1169 */
1170 protected ActionListener createKeyboardDownRightListener()
1171 {
1172 return new KeyboardDownRightHandler();
1173 }
1174
1175 /**
1176 * This method creates a new ActionListener for home key presses.
1177 *
1178 * @return A new ActionListener for home keys.
1179 *
1180 * @deprecated
1181 */
1182 protected ActionListener createKeyboardHomeListener()
1183 {
1184 return new KeyboardHomeHandler();
1185 }
1186
1187 /**
1188 * This method creates a new ActionListener for end key presses.i
1189 *
1190 * @return A new ActionListener for end keys.
1191 *
1192 * @deprecated 1.3
1193 */
1194 protected ActionListener createKeyboardEndListener()
1195 {
1196 return new KeyboardEndHandler();
1197 }
1198
1199 /**
1200 * This method creates a new ActionListener for resize toggle key events.
1201 *
1202 * @return A new ActionListener for resize toggle keys.
1203 *
1204 * @deprecated 1.3
1205 */
1206 protected ActionListener createKeyboardResizeToggleListener()
1207 {
1208 return new KeyboardResizeToggleHandler();
1209 }
1210
1211 /**
1212 * This method returns the orientation of the JSplitPane.
1213 *
1214 * @return The orientation of the JSplitPane.
1215 */
1216 public int getOrientation()
1217 {
1218 return splitPane.getOrientation();
1219 }
1220
1221 /**
1222 * This method sets the orientation of the JSplitPane.
1223 *
1224 * @param orientation The new orientation of the JSplitPane.
1225 */
1226 public void setOrientation(int orientation)
1227 {
1228 splitPane.setOrientation(orientation);
1229 }
1230
1231 /**
1232 * This method returns true if the JSplitPane is using continuous layout.
1233 *
1234 * @return True if the JSplitPane is using continuous layout.
1235 */
1236 public boolean isContinuousLayout()
1237 {
1238 return splitPane.isContinuousLayout();
1239 }
1240
1241 /**
1242 * This method sets the continuous layout property of the JSplitPane.
1243 *
1244 * @param b True if the JsplitPane is to use continuous layout.
1245 */
1246 public void setContinuousLayout(boolean b)
1247 {
1248 splitPane.setContinuousLayout(b);
1249 }
1250
1251 /**
1252 * This method returns the last location the divider was dragged to.
1253 *
1254 * @return The last location the divider was dragged to.
1255 */
1256 public int getLastDragLocation()
1257 {
1258 return lastDragLocation;
1259 }
1260
1261 /**
1262 * This method sets the last location the divider was dragged to.
1263 *
1264 * @param l The last location the divider was dragged to.
1265 */
1266 public void setLastDragLocation(int l)
1267 {
1268 lastDragLocation = l;
1269 }
1270
1271 /**
1272 * This method returns the BasicSplitPaneDivider that divides this
1273 * JSplitPane.
1274 *
1275 * @return The divider for the JSplitPane.
1276 */
1277 public BasicSplitPaneDivider getDivider()
1278 {
1279 return divider;
1280 }
1281
1282 /**
1283 * This method creates a nonContinuousLayoutDivider for use with the
1284 * JSplitPane in nonContinousLayout mode. The default divider is a gray
1285 * Canvas.
1286 *
1287 * @return The default nonContinousLayoutDivider.
1288 */
1289 protected Component createDefaultNonContinuousLayoutDivider()
1290 {
1291 if (nonContinuousLayoutDivider == null)
1292 {
1293 nonContinuousLayoutDivider = new Canvas();
1294 Color c = UIManager.getColor("SplitPaneDivider.draggingColor");
1295 nonContinuousLayoutDivider.setBackground(c);
1296 }
1297 return nonContinuousLayoutDivider;
1298 }
1299
1300 /**
1301 * This method sets the component to use as the nonContinuousLayoutDivider.
1302 *
1303 * @param newDivider The component to use as the nonContinuousLayoutDivider.
1304 */
1305 protected void setNonContinuousLayoutDivider(Component newDivider)
1306 {
1307 setNonContinuousLayoutDivider(newDivider, true);
1308 }
1309
1310 /**
1311 * This method sets the component to use as the nonContinuousLayoutDivider.
1312 *
1313 * @param newDivider The component to use as the nonContinuousLayoutDivider.
1314 * @param rememberSizes FIXME: document.
1315 */
1316 protected void setNonContinuousLayoutDivider(Component newDivider,
1317 boolean rememberSizes)
1318 {
1319 // FIXME: use rememberSizes for something
1320 nonContinuousLayoutDivider = newDivider;
1321 }
1322
1323 /**
1324 * This method returns the nonContinuousLayoutDivider.
1325 *
1326 * @return The nonContinuousLayoutDivider.
1327 */
1328 public Component getNonContinuousLayoutDivider()
1329 {
1330 return nonContinuousLayoutDivider;
1331 }
1332
1333 /**
1334 * This method returns the JSplitPane that this BasicSplitPaneUI draws.
1335 *
1336 * @return The JSplitPane.
1337 */
1338 public JSplitPane getSplitPane()
1339 {
1340 return splitPane;
1341 }
1342
1343 /**
1344 * This method creates the divider used normally with the JSplitPane.
1345 *
1346 * @return The default divider.
1347 */
1348 public BasicSplitPaneDivider createDefaultDivider()
1349 {
1350 if (divider == null)
1351 divider = new BasicSplitPaneDivider(this);
1352 return divider;
1353 }
1354
1355 /**
1356 * This method is called when JSplitPane's resetToPreferredSizes is called.
1357 * It resets the sizes of all components in the JSplitPane.
1358 *
1359 * @param jc The JSplitPane to reset.
1360 */
1361 public void resetToPreferredSizes(JSplitPane jc)
1362 {
1363 layoutManager.resetToPreferredSizes();
1364 }
1365
1366 /**
1367 * This method sets the location of the divider.
1368 *
1369 * @param jc The JSplitPane to set the divider location in.
1370 * @param location The new location of the divider.
1371 */
1372 public void setDividerLocation(JSplitPane jc, int location)
1373 {
1374 dividerLocationSet = true;
1375 splitPane.revalidate();
1376 splitPane.repaint();
1377 }
1378
1379 /**
1380 * This method returns the location of the divider.
1381 *
1382 * @param jc The JSplitPane to retrieve the location for.
1383 *
1384 * @return The location of the divider.
1385 */
1386 public int getDividerLocation(JSplitPane jc)
1387 {
1388 int loc;
1389 if (jc.getOrientation() == JSplitPane.HORIZONTAL_SPLIT)
1390 loc = divider.getX();
1391 else
1392 loc = divider.getY();
1393 return loc;
1394 }
1395
1396 /**
1397 * This method returns the smallest value possible for the location of the
1398 * divider.
1399 *
1400 * @param jc The JSplitPane.
1401 *
1402 * @return The minimum divider location.
1403 */
1404 public int getMinimumDividerLocation(JSplitPane jc)
1405 {
1406 int value = layoutManager.getInitialLocation(jc.getInsets());
1407 if (layoutManager.components[0] != null)
1408 value += layoutManager.minimumSizeOfComponent(0);
1409 return value;
1410 }
1411
1412 /**
1413 * This method returns the largest value possible for the location of the
1414 * divider.
1415 *
1416 * @param jc The JSplitPane.
1417 *
1418 * @return The maximum divider location.
1419 */
1420 public int getMaximumDividerLocation(JSplitPane jc)
1421 {
1422 int value = layoutManager.getInitialLocation(jc.getInsets())
1423 + layoutManager.getAvailableSize(jc.getSize(), jc.getInsets())
1424 - splitPane.getDividerSize();
1425 if (layoutManager.components[1] != null)
1426 value -= layoutManager.minimumSizeOfComponent(1);
1427 return value;
1428 }
1429
1430 /**
1431 * This method is called after the children of the JSplitPane are painted.
1432 *
1433 * @param jc The JSplitPane.
1434 * @param g The Graphics object to paint with.
1435 */
1436 public void finishedPaintingChildren(JSplitPane jc, Graphics g)
1437 {
1438 if (! splitPane.isContinuousLayout() && nonContinuousLayoutDivider != null
1439 && nonContinuousLayoutDivider.isVisible())
1440 javax.swing.SwingUtilities.paintComponent(g, nonContinuousLayoutDivider,
1441 null,
1442 nonContinuousLayoutDivider
1443 .getBounds());
1444 }
1445
1446 /**
1447 * This method is called to paint the JSplitPane.
1448 *
1449 * @param g The Graphics object to paint with.
1450 * @param jc The JSplitPane to paint.
1451 */
1452 public void paint(Graphics g, JComponent jc)
1453 {
1454 // TODO: What should be done here?
1455 }
1456
1457 /**
1458 * This method returns the preferred size of the JSplitPane.
1459 *
1460 * @param jc The JSplitPane.
1461 *
1462 * @return The preferred size of the JSplitPane.
1463 */
1464 public Dimension getPreferredSize(JComponent jc)
1465 {
1466 return layoutManager.preferredLayoutSize(jc);
1467 }
1468
1469 /**
1470 * This method returns the minimum size of the JSplitPane.
1471 *
1472 * @param jc The JSplitPane.
1473 *
1474 * @return The minimum size of the JSplitPane.
1475 */
1476 public Dimension getMinimumSize(JComponent jc)
1477 {
1478 return layoutManager.minimumLayoutSize(jc);
1479 }
1480
1481 /**
1482 * This method returns the maximum size of the JSplitPane.
1483 *
1484 * @param jc The JSplitPane.
1485 *
1486 * @return The maximum size of the JSplitPane.
1487 */
1488 public Dimension getMaximumSize(JComponent jc)
1489 {
1490 return layoutManager.maximumLayoutSize(jc);
1491 }
1492
1493 /**
1494 * This method returns the border insets of the current border.
1495 *
1496 * @param jc The JSplitPane.
1497 *
1498 * @return The current border insets.
1499 */
1500 public Insets getInsets(JComponent jc)
1501 {
1502 return splitPane.getBorder().getBorderInsets(splitPane);
1503 }
1504
1505 /**
1506 * This method resets the current layout manager. The type of layout manager
1507 * is dependent on the current orientation.
1508 */
1509 protected void resetLayoutManager()
1510 {
1511 if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT)
1512 layoutManager = new BasicHorizontalLayoutManager();
1513 else
1514 layoutManager = new BasicVerticalLayoutManager();
1515 getSplitPane().setLayout(layoutManager);
1516 layoutManager.updateComponents();
1517
1518 // invalidating by itself does not invalidate the layout.
1519 getSplitPane().revalidate();
1520 }
1521
1522 /**
1523 * This method is called when dragging starts. It resets lastDragLocation
1524 * and dividerSize.
1525 */
1526 protected void startDragging()
1527 {
1528 Component left = splitPane.getLeftComponent();
1529 Component right = splitPane.getRightComponent();
1530 dividerSize = divider.getDividerSize();
1531 setLastDragLocation(-1);
1532
1533 if ((left != null && !left.isLightweight())
1534 || (right != null && !right.isLightweight()))
1535 draggingHW = true;
1536
1537 if (splitPane.isContinuousLayout())
1538 nonContinuousLayoutDivider.setVisible(false);
1539 else
1540 {
1541 nonContinuousLayoutDivider.setVisible(true);
1542 nonContinuousLayoutDivider.setBounds(divider.getBounds());
1543 }
1544 }
1545
1546 /**
1547 * This method is called whenever the divider is dragged. If the JSplitPane
1548 * is in continuousLayout mode, the divider needs to be moved and the
1549 * JSplitPane needs to be laid out.
1550 *
1551 * @param location The new location of the divider.
1552 */
1553 protected void dragDividerTo(int location)
1554 {
1555 location = validLocation(location);
1556 if (beginDragDividerLocation == -1)
1557 beginDragDividerLocation = location;
1558
1559 if (splitPane.isContinuousLayout())
1560 splitPane.setDividerLocation(location);
1561 else
1562 {
1563 Point p = nonContinuousLayoutDivider.getLocation();
1564 if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT)
1565 p.x = location;
1566 else
1567 p.y = location;
1568 nonContinuousLayoutDivider.setLocation(p);
1569 }
1570 setLastDragLocation(location);
1571 splitPane.repaint();
1572 }
1573
1574 /**
1575 * This method is called when the dragging is finished.
1576 *
1577 * @param location The location where the drag finished.
1578 */
1579 protected void finishDraggingTo(int location)
1580 {
1581 if (nonContinuousLayoutDivider != null)
1582 nonContinuousLayoutDivider.setVisible(false);
1583 draggingHW = false;
1584 location = validLocation(location);
1585 splitPane.setDividerLocation(location);
1586 splitPane.setLastDividerLocation(beginDragDividerLocation);
1587 beginDragDividerLocation = -1;
1588 }
1589
1590 /**
1591 * This method returns the width of one of the sides of the divider's border.
1592 *
1593 * @return The width of one side of the divider's border.
1594 *
1595 * @deprecated 1.3
1596 */
1597 protected int getDividerBorderSize()
1598 {
1599 if (getOrientation() == JSplitPane.HORIZONTAL_SPLIT)
1600 return divider.getBorder().getBorderInsets(divider).left;
1601 else
1602 return divider.getBorder().getBorderInsets(divider).top;
1603 }
1604
1605 /**
1606 * This is a helper method that returns a valid location for the divider
1607 * when dragging.
1608 *
1609 * @param location The location to check.
1610 *
1611 * @return A valid location.
1612 */
1613 private int validLocation(int location)
1614 {
1615 int min = getMinimumDividerLocation(splitPane);
1616 int max = getMaximumDividerLocation(splitPane);
1617 if (min > 0 && location < min)
1618 return min;
1619 if (max > 0 && location > max)
1620 return max;
1621 return location;
1622 }
1623 }