001/* BasicSplitPaneUI.java --
002   Copyright (C) 2003, 2004, 2005, 2006, Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.Canvas;
042import java.awt.Color;
043import java.awt.Component;
044import java.awt.Container;
045import java.awt.Dimension;
046import java.awt.Graphics;
047import java.awt.Insets;
048import java.awt.LayoutManager2;
049import java.awt.Point;
050import java.awt.event.ActionEvent;
051import java.awt.event.ActionListener;
052import java.awt.event.FocusAdapter;
053import java.awt.event.FocusEvent;
054import java.awt.event.FocusListener;
055import java.beans.PropertyChangeEvent;
056import java.beans.PropertyChangeListener;
057
058import javax.swing.AbstractAction;
059import javax.swing.ActionMap;
060import javax.swing.InputMap;
061import javax.swing.JComponent;
062import javax.swing.JSlider;
063import javax.swing.JSplitPane;
064import javax.swing.KeyStroke;
065import javax.swing.LookAndFeel;
066import javax.swing.SwingConstants;
067import javax.swing.SwingUtilities;
068import javax.swing.UIManager;
069import javax.swing.plaf.ActionMapUIResource;
070import javax.swing.plaf.ComponentUI;
071import javax.swing.plaf.SplitPaneUI;
072import javax.swing.plaf.UIResource;
073
074/**
075 * This is the Basic Look and Feel implementation of the SplitPaneUI  class.
076 */
077public 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}