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    }