Logo Search packages:      
Sourcecode: classpath version File versions

BasicSliderUI.java

/* BasicSliderUI.java --
   Copyright (C) 2004 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing.plaf.basic;

import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Dictionary;
import java.util.Enumeration;

import javax.swing.BoundedRangeModel;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MouseInputAdapter;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.SliderUI;

/**
 * <p>
 * BasicSliderUI.java This is the UI delegate in the Basic look and feel that
 * paints JSliders.
 * </p>
 * 
 * <p>
 * The UI delegate keeps track of 6 rectangles that place the various parts of
 * the JSlider inside the component.
 * </p>
 * 
 * <p>
 * The rectangles are organized as follows:
 * </p>
 * <pre>
 *     +-------------------------------------------------------+ <-- focusRect
 *     |                                                       |
 *     |  +==+-------------------+==+--------------------+==+<------ contentRect
 *     |  |  |                   |  |<---thumbRect       |  |  |
 *     |  |  |    TRACK          |  |                    |<--------- trackRect
 *     |  |  +-------------------+==+--------------------+  |  |
 *     |  |  |                                           |  |  |
 *     |  |  |          TICKS GO HERE                    |<-------- tickRect
 *     |  |  |                                           |  |  |
 *     |  +==+-------------------------------------------+==+  |
 *     |  |  |                                           |  |  |
 *     |  |  |                                           |  |<----- labelRect
 *     |  |  |                 LABELS GO HERE            |  |  |
 *     |  |  |                                           |  |  |
 *     |  |  |                                           |  |  |
 *     |  |  |                                           |  |  |
 *     |  |  |                                           |  |  |
 *     |  |                                              |  |  |
 * </pre>
 * 
 * <p>
 * The space between the contentRect and the focusRect are the FocusInsets.
 * </p>
 * 
 * <p>
 * The space between the focusRect and the component bounds is the insetCache
 * which are the component's insets.
 * </p>
 * 
 * <p>
 * The top of the thumb is the top of the contentRect. The trackRect has to be
 * as tall as the thumb.
 * </p>
 * 
 * <p>
 * The trackRect and tickRect do not start from the left edge of the
 * focusRect. They are trackBuffer away from each side of the focusRect. This
 * is so that the thumb has room to move.
 * </p>
 * 
 * <p>
 * The labelRect does start right against the contentRect's left and right
 * edges and it gets all remaining space.
 * </p>
 */
00137 public class BasicSliderUI extends SliderUI
{
  /**
   * Helper class that listens to the {@link JSlider}'s model for changes.
   */
00142   protected class ChangeHandler implements ChangeListener
  {
    /**
     * Called when the slider's model has been altered. The UI delegate should
     * recalculate any rectangles that are dependent on the model for their
     * positions and repaint.
     *
     * @param e A static {@link ChangeEvent} passed from the model.
     */
00151     public void stateChanged(ChangeEvent e)
    {
      // Maximum, minimum, and extent values will be taken
      // care of automatically when the slider is repainted.
      // Only thing that needs recalculation is the thumb.
      calculateThumbLocation();
      slider.repaint();
    }
  }

  /**
   * Helper class that listens for resize events.
   */
00164   protected class ComponentHandler extends ComponentAdapter
  {
    /**
     * Called when the size of the component changes. The UI delegate should
     * recalculate any rectangles that are dependent on the model for their
     * positions and repaint.
     *
     * @param e A {@link ComponentEvent}.
     */
00173     public void componentResized(ComponentEvent e)
    {
      calculateGeometry();

      slider.revalidate();
      slider.repaint();
    }
  }

  /**
   * Helper class that listens for focus events.
   */
00185   protected class FocusHandler implements FocusListener
  {
    /**
     * Called when the {@link JSlider} has gained focus.  It should repaint
     * the slider with the focus drawn.
     *
     * @param e A {@link FocusEvent}.
     */
00193     public void focusGained(FocusEvent e)
    {
      // FIXME: implement.
    }

    /**
     * Called when the {@link JSlider} has lost focus. It  should repaint the
     * slider without the focus drawn.
     *
     * @param e A {@link FocusEvent}.
     */
00204     public void focusLost(FocusEvent e)
    {
      // FIXME: implement.
    }
  }

  /**
   * Helper class that listens for changes to the properties of the {@link
   * JSlider}.
   */
00214   protected class PropertyChangeHandler implements PropertyChangeListener
  {
    /**
     * Called when one of the properties change. The UI should recalculate any
     * rectangles if necessary and repaint.
     *
     * @param e A {@link PropertyChangeEvent}.
     */
00222     public void propertyChange(PropertyChangeEvent e)
    {
      // Check for orientation changes.
      if (e.getPropertyName().equals(JSlider.ORIENTATION_CHANGED_PROPERTY))
      recalculateIfOrientationChanged();
      else if (e.getPropertyName().equals(JSlider.MODEL_CHANGED_PROPERTY))
        {
        BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue();
        oldModel.removeChangeListener(changeListener);
        slider.getModel().addChangeListener(changeListener);
        calculateThumbLocation();
        }

      // elif the componentOrientation changes (this is a bound property,
      // just undocumented) we change leftToRightCache. In Sun's 
      // implementation, the LTR cache changes on a repaint. This is strange
      // since there is no need to do so. We could events here and 
      // update the cache. 
      // elif the border/insets change, we recalculateInsets.
      slider.repaint();
    }
  }

  /**
   * Helper class that listens to our swing timer. This class is responsible
   * for listening to the timer and moving the thumb in the proper direction
   * every interval.
   */
00250   protected class ScrollListener implements ActionListener
  {
    /** Indicates which direction the thumb should scroll. */
00253     private transient int direction;

    /** Indicates whether we should scroll in blocks or in units. */
00256     private transient boolean block;

    /**
     * Creates a new ScrollListener object.
     */
00261     public ScrollListener()
    {
      direction = POSITIVE_SCROLL;
      block = false;
    }

    /**
     * Creates a new ScrollListener object.
     *
     * @param dir The direction to scroll in.
     * @param block If movement will be in blocks.
     */
00273     public ScrollListener(int dir, boolean block)
    {
      direction = dir;
      this.block = block;
    }

    /**
     * Called every time the swing timer reaches its interval. If the thumb
     * needs to move, then this method will move the thumb one block or  unit
     * in the direction desired. Otherwise, the timer can be stopped.
     *
     * @param e An {@link ActionEvent}.
     */
00286     public void actionPerformed(ActionEvent e)
    {
      if (! trackListener.shouldScroll(direction))
        {
        scrollTimer.stop();
        return;
        }

      if (block)
      scrollByBlock(direction);
      else
      scrollByUnit(direction);
    }

    /**
     * Sets the direction to scroll in.
     *
     * @param direction The direction to scroll in.
     */
00305     public void setDirection(int direction)
    {
      this.direction = direction;
    }

    /**
     * Sets whether movement will be in blocks.
     *
     * @param block If movement will be in blocks.
     */
00315     public void setScrollByBlock(boolean block)
    {
      this.block = block;
    }
  }

  /**
   * Helper class that listens for mouse events.
   */
00324   public class TrackListener extends MouseInputAdapter
  {
    /** The current X position of the mouse. */
00327     protected int currentMouseX;

    /** The current Y position of the mouse. */
00330     protected int currentMouseY;

    /**
     * The offset between the current slider value and the cursor's position.
     */
00335     protected int offset;

    /**
     * Called when the mouse has been dragged. This should find the mouse's
     * current position and adjust the value of the {@link JSlider}
     * accordingly.
     *
     * @param e A {@link MouseEvent}
     */
00344     public void mouseDragged(MouseEvent e)
    {
      currentMouseX = e.getX();
      currentMouseY = e.getY();
      if (slider.getValueIsAdjusting())
        {
        int value;
        if (slider.getOrientation() == JSlider.HORIZONTAL)
          value = valueForXPosition(currentMouseX) - offset;
        else
          value = valueForYPosition(currentMouseY) - offset;

        slider.setValue(value);
        }
    }

    /**
     * Called when the mouse has moved over a component but no buttons have
     * been pressed yet.
     *
     * @param e A {@link MouseEvent}
     */
00366     public void mouseMoved(MouseEvent e)
    {
      // Don't care that we're moved unless we're dragging.
    }

    /**
     * Called when the mouse is pressed. When the press occurs on the thumb
     * itself, the {@link JSlider} should have its value set to where the
     * mouse was pressed. If the press occurs on the track, then the thumb
     * should move one block towards the direction of the mouse.
     *
     * @param e A {@link MouseEvent}
     */
00379     public void mousePressed(MouseEvent e)
    {
      currentMouseX = e.getX();
      currentMouseY = e.getY();

      int value;
      if (slider.getOrientation() == JSlider.HORIZONTAL)
      value = valueForXPosition(currentMouseX);
      else
      value = valueForYPosition(currentMouseY);

      if (slider.getSnapToTicks())
      value = findClosestTick(value);

      // If the thumb is hit, then we don't need to set the timers to move it. 
      if (! thumbRect.contains(e.getPoint()))
        {
        // The mouse has hit some other part of the slider.
        // The value moves no matter where in the slider you hit.
        if (value > slider.getValue())
          scrollDueToClickInTrack(POSITIVE_SCROLL);
        else
          scrollDueToClickInTrack(NEGATIVE_SCROLL);
        }
      else
        {
        slider.setValueIsAdjusting(true);
        offset = value - slider.getValue();
        }
    }

    /**
     * Called when the mouse is released.  This should stop the timer that
     * scrolls the thumb.
     *
     * @param e A {@link MouseEvent}
     */
00416     public void mouseReleased(MouseEvent e)
    {
      currentMouseX = e.getX();
      currentMouseY = e.getY();

      if (slider.getValueIsAdjusting())
        {
        slider.setValueIsAdjusting(false);
        if (slider.getSnapToTicks())
          slider.setValue(findClosestTick(slider.getValue()));
        }
      if (scrollTimer != null)
      scrollTimer.stop();
    }

    /**
     * Indicates whether the thumb should scroll in the given direction.
     *
     * @param direction The direction to check.
     *
     * @return True if the thumb should move in that direction.
     */
00438     public boolean shouldScroll(int direction)
    {
      int value;
      if (slider.getOrientation() == JSlider.HORIZONTAL)
      value = valueForXPosition(currentMouseX);
      else
      value = valueForYPosition(currentMouseY);

      if (direction == POSITIVE_SCROLL)
      return (value > slider.getValue());
      else
      return (value < slider.getValue());
    }
  }

  /** The preferred height of the thumb. */
00454   private transient int thumbHeight;

  /** The preferred width of the thumb. */
00457   private transient int thumbWidth;

  /** The preferred height of the tick rectangle. */
00460   private transient int tickHeight;

  /** Listener for changes from the model. */
00463   protected ChangeListener changeListener;

  /** Listener for changes to the {@link JSlider}. */
00466   protected PropertyChangeListener propertyChangeListener;

  /** Listener for the scrollTimer. */
00469   protected ScrollListener scrollListener;

  /** Listener for component resizing. */
00472   protected ComponentListener componentListener;

  /** Listener for focus handling. */
00475   protected FocusListener focusListener;

  /** Listener for mouse events. */
00478   protected TrackListener trackListener;

  /** The insets between the FocusRectangle and the ContentRectangle. */
00481   protected Insets focusInsets;

  /** The {@link JSlider}'s insets. */
00484   protected Insets insetCache;

  /** Rectangle describing content bounds. See diagram above. */
00487   protected Rectangle contentRect;

  /** Rectangle describing focus bounds. See diagram above. */
00490   protected Rectangle focusRect;

  /** Rectangle describing the thumb's bounds. See diagram above. */
00493   protected Rectangle thumbRect;

  /** Rectangle describing the tick bounds. See diagram above. */
00496   protected Rectangle tickRect;

  /** Rectangle describing the label bounds. See diagram above. */
00499   protected Rectangle labelRect;

  /** Rectangle describing the track bounds. See diagram above. */
00502   protected Rectangle trackRect;

  /** FIXME: use this somewhere. */
00505   public static final int MAX_SCROLL = 2;

  /** FIXME: use this somewhere. */
00508   public static final int MIN_SCROLL = -2;

  /** A constant describing scrolling towards the minimum. */
00511   public static final int NEGATIVE_SCROLL = -1;

  /** A constant describing scrolling towards the maximum. */
00514   public static final int POSITIVE_SCROLL = 1;

  /** The gap between the edges of the contentRect and trackRect. */
00517   protected int trackBuffer;

  /** Whether this slider is actually drawn left to right. */
00520   protected boolean leftToRightCache;

  /** A timer that periodically moves the thumb. */
00523   protected Timer scrollTimer;

  /** A reference to the {@link JSlider} that this UI was created for. */
00526   protected JSlider slider;

  /** The shadow color. */
00529   private transient Color shadowColor;

  /** The highlight color. */
00532   private transient Color highlightColor;

  /** The focus color. */
00535   private transient Color focusColor;

  /**
   * Creates a new Basic look and feel Slider UI.
   *
   * @param b The {@link JSlider} that this UI was created for.
   */
00542   public BasicSliderUI(JSlider b)
  {
    super();
  }

  /**
   * Gets the shadow color to be used for this slider. The shadow color is the
   * color used for drawing the top and left edges of the track.
   *
   * @return The shadow color.
   */
00553   protected Color getShadowColor()
  {
    return shadowColor;
  }

  /**
   * Gets the highlight color to be used for this slider. The highlight color
   * is the color used for drawing the bottom and right edges of the track.
   *
   * @return The highlight color.
   */
00564   protected Color getHighlightColor()
  {
    return highlightColor;
  }

  /**
   * Gets the focus color to be used for this slider. The focus color is the
   * color used for drawing the focus rectangle when the component gains
   * focus.
   *
   * @return The focus color.
   */
00576   protected Color getFocusColor()
  {
    return focusColor;
  }

  /**
   * Factory method to create a BasicSliderUI for the given {@link
   * JComponent}, which should be a {@link JSlider}.
   *
   * @param b The {@link JComponent} a UI is being created for.
   *
   * @return A BasicSliderUI for the {@link JComponent}.
   */
00589   public static ComponentUI createUI(JComponent b)
  {
    return new BasicSliderUI((JSlider) b);
  }

  /**
   * Installs and initializes all fields for this UI delegate. Any properties
   * of the UI that need to be initialized and/or set to defaults will be
   * done now. It will also install any listeners necessary.
   *
   * @param c The {@link JComponent} that is having this UI installed.
   */
00601   public void installUI(JComponent c)
  {
    super.installUI(c);
    if (c instanceof JSlider)
      {
      slider = (JSlider) c;

      focusRect = new Rectangle();
      contentRect = new Rectangle();
      thumbRect = new Rectangle();
      trackRect = new Rectangle();
      tickRect = new Rectangle();
      labelRect = new Rectangle();

      insetCache = slider.getInsets();
      leftToRightCache = ! slider.getInverted();

      scrollTimer = new Timer(200, null);
      scrollTimer.setRepeats(true);

      installDefaults(slider);
      installListeners(slider);
      installKeyboardActions(slider);

      calculateFocusRect();

      calculateContentRect();
      calculateThumbSize();
      calculateTrackBuffer();
      calculateTrackRect();
      calculateThumbLocation();

      calculateTickRect();
      calculateLabelRect();
      }
  }

  /**
   * Performs the opposite of installUI. Any properties or resources that need
   * to be cleaned up will be done now. It will also uninstall any listeners
   * it has. In addition, any properties of this UI will be nulled.
   *
   * @param c The {@link JComponent} that is having this UI uninstalled.
   */
00645   public void uninstallUI(JComponent c)
  {
    super.uninstallUI(c);

    uninstallKeyboardActions(slider);
    uninstallListeners(slider);

    scrollTimer = null;

    focusRect = null;
    contentRect = null;
    thumbRect = null;
    trackRect = null;
    tickRect = null;
    labelRect = null;

    focusInsets = null;
  }

  /**
   * Initializes any default properties that this UI has from the defaults for
   * the Basic look and feel.
   *
   * @param slider The {@link JSlider} that is having this UI installed.
   */
00670   protected void installDefaults(JSlider slider)
  {
    UIDefaults defaults = UIManager.getLookAndFeelDefaults();

    slider.setForeground(defaults.getColor("Slider.foreground"));
    slider.setBackground(defaults.getColor("Slider.background"));
    shadowColor = defaults.getColor("Slider.shadow");
    highlightColor = defaults.getColor("Slider.highlight");
    focusColor = defaults.getColor("Slider.focus");
    slider.setBorder(defaults.getBorder("Slider.border"));
    slider.setOpaque(true);

    thumbHeight = defaults.getInt("Slider.thumbHeight");
    thumbWidth = defaults.getInt("Slider.thumbWidth");
    tickHeight = defaults.getInt("Slider.tickHeight");

    focusInsets = defaults.getInsets("Slider.focusInsets");
  }

  /**
   * Creates a new {@link TrackListener}.
   *
   * @param slider The {@link JSlider} that this {@link TrackListener} is
   *        created for.
   *
   * @return A new {@link TrackListener}.
   */
00697   protected TrackListener createTrackListener(JSlider slider)
  {
    return new TrackListener();
  }

  /**
   * Creates a new {@link ChangeListener}.
   *
   * @param slider The {@link JSlider} that this {@link ChangeListener} is
   *        created for.
   *
   * @return A new {@link ChangeListener}.
   */
00710   protected ChangeListener createChangeListener(JSlider slider)
  {
    return new ChangeHandler();
  }

  /**
   * Creates a new {@link ComponentListener}.
   *
   * @param slider The {@link JSlider} that this {@link ComponentListener} is
   *        created for.
   *
   * @return A new {@link ComponentListener}.
   */
00723   protected ComponentListener createComponentListener(JSlider slider)
  {
    return new ComponentHandler();
  }

  /**
   * Creates a new {@link FocusListener}.
   *
   * @param slider The {@link JSlider} that this {@link FocusListener} is
   *        created for.
   *
   * @return A new {@link FocusListener}.
   */
00736   protected FocusListener createFocusListener(JSlider slider)
  {
    return new FocusHandler();
  }

  /**
   * Creates a new {@link ScrollListener}.
   *
   * @param slider The {@link JSlider} that this {@link ScrollListener} is
   *        created for.
   *
   * @return A new {@link ScrollListener}.
   */
00749   protected ScrollListener createScrollListener(JSlider slider)
  {
    return new ScrollListener();
  }

  /**
   * Creates a new {@link PropertyChangeListener}.
   *
   * @param slider The {@link JSlider} that this {@link
   *        PropertyChangeListener} is created for.
   *
   * @return A new {@link PropertyChangeListener}.
   */
00762   protected PropertyChangeListener createPropertyChangeListener(JSlider slider)
  {
    return new PropertyChangeHandler();
  }

  /**
   * Creates and registers all the listeners for this UI delegate. This
   * includes creating the ScrollListener and registering it to the timer.
   *
   * @param slider The {@link JSlider} is having listeners installed.
   */
00773   protected void installListeners(JSlider slider)
  {
    propertyChangeListener = createPropertyChangeListener(slider);
    componentListener = createComponentListener(slider);
    trackListener = createTrackListener(slider);
    focusListener = createFocusListener(slider);
    changeListener = createChangeListener(slider);
    scrollListener = createScrollListener(slider);

    slider.addPropertyChangeListener(propertyChangeListener);
    slider.addComponentListener(componentListener);
    slider.addMouseListener(trackListener);
    slider.addMouseMotionListener(trackListener);
    slider.addFocusListener(focusListener);
    slider.getModel().addChangeListener(changeListener);

    scrollTimer.addActionListener(scrollListener);
  }

  /**
   * Unregisters all the listeners that this UI delegate was using. In
   * addition, it will also null any listeners that it was using.
   *
   * @param slider The {@link JSlider} that is having listeners removed.
   */
00798   protected void uninstallListeners(JSlider slider)
  {
    slider.removePropertyChangeListener(propertyChangeListener);
    slider.removeComponentListener(componentListener);
    slider.removeMouseListener(trackListener);
    slider.removeMouseMotionListener(trackListener);
    slider.removeFocusListener(focusListener);
    slider.getModel().removeChangeListener(changeListener);

    scrollTimer.removeActionListener(scrollListener);

    propertyChangeListener = null;
    componentListener = null;
    trackListener = null;
    focusListener = null;
    changeListener = null;
    scrollListener = null;
  }

  /**
   * Installs any keyboard actions. The list of keys that need to be bound are
   * listed in Basic look and feel's defaults.
   *
   * @param slider The {@link JSlider} that is having keyboard actions
   *        installed.
   */
00824   protected void installKeyboardActions(JSlider slider)
  {
    // FIXME: implement.
  }

  /**
   * Uninstalls any keyboard actions. The list of keys used  are listed in
   * Basic look and feel's defaults.
   *
   * @param slider The {@link JSlider} that is having keyboard actions
   *        uninstalled.
   */
00836   protected void uninstallKeyboardActions(JSlider slider)
  {
    // FIXME: implement.
  }

  /* XXX: This is all after experimentation with SUN's implementation.

     PreferredHorizontalSize seems to be 200x21.
     PreferredVerticalSize seems to be 21x200.

     MinimumHorizontalSize seems to be 36x21.
     MinimumVerticalSize seems to be 21x36.

     PreferredSize seems to be 200x63. Or Components.getBounds?

     MinimumSize seems to be 36x63.

     MaximumSize seems to be 32767x63.
   */

  /**
   * This method returns the preferred size when the slider is horizontally
   * oriented.
   *
   * @return The dimensions of the preferred horizontal size.
   */
00862   public Dimension getPreferredHorizontalSize()
  {
    Insets insets = slider.getInsets();

    // The width should cover all the labels (which are usually the
    // deciding factor of the width)
    int width = getWidthOfWidestLabel() * (slider.getLabelTable() == null ? 0
                                                                          : slider.getLabelTable()
                                                                                  .size());

    // If there are not enough labels.
    // This number is pretty much arbitrary, but it looks nice.
    if (width < 200)
      width = 200;

    // We can only draw inside of the focusRectangle, so we have to
    // pad it with insets.
    width += insets.left + insets.right + focusInsets.left + focusInsets.right;

    // Height is determined by the thumb, the ticks and the labels.
    int height = thumbHeight;

    if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0
        || slider.getMinorTickSpacing() > 0)
      height += tickHeight;

    if (slider.getPaintLabels())
      height += getHeightOfTallestLabel();

    height += insets.top + insets.bottom + focusInsets.top
    + focusInsets.bottom;

    return new Dimension(width, height);
  }

  /**
   * This method returns the preferred size when the slider is vertically
   * oriented.
   *
   * @return The dimensions of the preferred vertical size.
   */
00903   public Dimension getPreferredVerticalSize()
  {
    Insets insets = slider.getInsets();

    int height = getHeightOfTallestLabel() * (slider.getLabelTable() == null
                                              ? 0 : slider.getLabelTable()
                                                          .size());

    if (height < 200)
      height = 200;

    height += insets.top + insets.bottom + focusInsets.top
    + focusInsets.bottom;

    int width = thumbHeight;

    if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0
        || slider.getMinorTickSpacing() > 0)
      width += tickHeight;

    if (slider.getPaintLabels())
      width += getWidthOfWidestLabel();

    width += insets.left + insets.right + focusInsets.left + focusInsets.right;

    return new Dimension(width, height);
  }

  /**
   * This method returns the minimum size when the slider is horizontally
   * oriented.
   *
   * @return The dimensions of the minimum horizontal size.
   */
00937   public Dimension getMinimumHorizontalSize()
  {
    return getPreferredHorizontalSize();
  }

  /**
   * This method returns the minimum size of the slider when it  is vertically
   * oriented.
   *
   * @return The dimensions of the minimum vertical size.
   */
00948   public Dimension getMinimumVerticalSize()
  {
    return getPreferredVerticalSize();
  }

  /**
   * This method returns the preferred size of the component. If it returns
   * null, then it is up to the Layout Manager to give the {@link JComponent}
   * a size.
   *
   * @param c The {@link JComponent} to find the preferred size for.
   *
   * @return The dimensions of the preferred size.
   */
00962   public Dimension getPreferredSize(JComponent c)
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      return getPreferredHorizontalSize();
    else
      return getPreferredVerticalSize();
  }

  /**
   * This method returns the minimum size for this {@link JSlider}  for this
   * look and feel. If it returns null, then it is up to the Layout Manager
   * to give the {@link JComponent} a size.
   *
   * @param c The {@link JComponent} to find the minimum size for.
   *
   * @return The dimensions of the minimum size.
   */
00979   public Dimension getMinimumSize(JComponent c)
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      return getPreferredHorizontalSize();
    else
      return getPreferredVerticalSize();
  }

  /**
   * This method returns the maximum size for this {@link JSlider} for this
   * look and feel. If it returns null, then it is up to the Layout Manager
   * to give the {@link JComponent} a size.
   *
   * @param c The {@link JComponent} to find a maximum size for.
   *
   * @return The dimensions of the maximum size.
   */
00996   public Dimension getMaximumSize(JComponent c)
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      return getPreferredHorizontalSize();
    else
      return getPreferredVerticalSize();
  }

  /**
   * This method calculates all the sizes of the rectangles by delegating to
   * the helper methods calculateXXXRect.
   */
01008   protected void calculateGeometry()
  {
    calculateFocusRect();
    calculateContentRect();
    calculateThumbSize();
    calculateTrackBuffer();
    calculateTrackRect();
    calculateTickRect();
    calculateLabelRect();
    calculateThumbLocation();
  }

  /**
   * This method calculates the size and position of the focusRect. This
   * method does not need to be called if the orientation changes.
   */
01024   protected void calculateFocusRect()
  {
    insetCache = slider.getInsets();
    focusRect = SwingUtilities.calculateInnerArea(slider, focusRect);

    if (focusRect.width < 0)
      focusRect.width = 0;
    if (focusRect.height < 0)
      focusRect.height = 0;
  }

  /**
   * This method calculates the size but not the position of the thumbRect. It
   * must take into account the orientation of the slider.
   */
01039   protected void calculateThumbSize()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      if (thumbWidth > contentRect.width)
        thumbRect.width = contentRect.width / 4;
      else
        thumbRect.width = thumbWidth;
      if (thumbHeight > contentRect.height)
        thumbRect.height = contentRect.height;
      else
        thumbRect.height = thumbHeight;
      }
    else
      {
      // The thumb gets flipped when inverted, so thumbWidth 
      // actually is the height and vice versa.
      if (thumbWidth > contentRect.height)
        thumbRect.height = contentRect.height / 4;
      else
        thumbRect.height = thumbWidth;
      if (thumbHeight > contentRect.width)
        thumbRect.width = contentRect.width;
      else
        thumbRect.width = thumbHeight;
      }
  }

  /**
   * This method calculates the size and position of the contentRect. This
   * method does not need to be  called if the orientation changes.
   */
01071   protected void calculateContentRect()
  {
    contentRect.x = focusRect.x + focusInsets.left;
    contentRect.y = focusRect.y + focusInsets.top;
    contentRect.width = focusRect.width - focusInsets.left - focusInsets.right;
    contentRect.height = focusRect.height - focusInsets.top
                         - focusInsets.bottom;

    if (contentRect.width < 0)
      contentRect.width = 0;
    if (contentRect.height < 0)
      contentRect.height = 0;
  }

  /**
   * Calculates the position of the thumbRect based on the current value of
   * the slider. It must take into  account the orientation of the slider.
   */
01089   protected void calculateThumbLocation()
  {
    int value = slider.getValue();

    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      thumbRect.x = xPositionForValue(value) - thumbRect.width / 2;
      thumbRect.y = contentRect.y;
      }
    else
      {
      thumbRect.x = contentRect.x;
      thumbRect.y = yPositionForValue(value) - thumbRect.height / 2;
      }
  }

  /**
   * Calculates the gap size between the left edge of the contentRect and the
   * left edge of the trackRect.
   */
01109   protected void calculateTrackBuffer()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      trackBuffer = thumbRect.width;
    else
      trackBuffer = thumbRect.height;
  }

  /**
   * This method returns the size of the thumbRect.
   *
   * @return The dimensions of the thumb.
   */
01122   protected Dimension getThumbSize()
  {
    // This is really just the bounds box for the thumb.
    // The thumb will actually be pointed (like a rectangle + triangle at bottom)
    return thumbRect.getSize();
  }

  /**
   * Calculates the size and position of the trackRect. It must take into
   * account the orientation of the slider.
   */
01133   protected void calculateTrackRect()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      trackRect.x = contentRect.x + trackBuffer;
      trackRect.y = contentRect.y;
      trackRect.width = contentRect.width - 2 * trackBuffer;
      trackRect.height = thumbRect.height;
      }
    else
      {
      trackRect.x = contentRect.x;
      trackRect.y = contentRect.y + trackBuffer;
      trackRect.width = thumbRect.width;
      trackRect.height = contentRect.height - 2 * trackBuffer;
      }
  }

  /**
   * This method returns the height of the tick area box if the slider  is
   * horizontal and the width of the tick area box is the slider is vertical.
   * It not necessarily how long the ticks will be. If a gap between the edge
   * of tick box and the actual tick is desired, then that will need to be
   * handled in the tick painting methods.
   *
   * @return The height (or width if the slider is vertical) of the tick
   *         rectangle.
   */
01161   protected int getTickLength()
  {
    return tickHeight;
  }

  /**
   * This method calculates the size and position of the tickRect. It must
   * take into account the orientation of the slider.
   */
01170   protected void calculateTickRect()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      tickRect.x = trackRect.x;
      tickRect.y = trackRect.y + trackRect.height;
      tickRect.width = trackRect.width;
      tickRect.height = getTickLength();

      if (tickRect.y + tickRect.height > contentRect.y + contentRect.height)
        tickRect.height = contentRect.y + contentRect.height - tickRect.y;
      }
    else
      {
      tickRect.x = trackRect.x + trackRect.width;
      tickRect.y = trackRect.y;
      tickRect.width = getTickLength();
      tickRect.height = trackRect.height;

      if (tickRect.x + tickRect.width > contentRect.x + contentRect.width)
        tickRect.width = contentRect.x + contentRect.width - tickRect.x;
      }
  }

  /**
   * This method calculates the size and position of the labelRect. It must
   * take into account the orientation of the slider.
   */
01198   protected void calculateLabelRect()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      labelRect.x = contentRect.x;
      labelRect.y = tickRect.y + tickRect.height;
      labelRect.width = contentRect.width;
      labelRect.height = contentRect.height - labelRect.y;
      }
    else
      {
      labelRect.x = tickRect.x + tickRect.width;
      labelRect.y = contentRect.y;
      labelRect.width = contentRect.width - labelRect.x;
      labelRect.height = contentRect.height;
      }
  }

  /**
   * This method returns the width of the widest label  in the slider's label
   * table.
   *
   * @return The width of the widest label or 0 if no label table exists.
   */
01222   protected int getWidthOfWidestLabel()
  {
    int widest = 0;
    Component label;

    if (slider.getLabelTable() == null)
      return 0;

    Dimension pref;
    for (Enumeration list = slider.getLabelTable().elements();
         list.hasMoreElements();)
      {
      Object comp = list.nextElement();
      if (! (comp instanceof Component))
        continue;
      label = (Component) comp;
      pref = label.getPreferredSize();
      if (pref != null && pref.width > widest)
        widest = pref.width;
      }
    return widest;
  }

  /**
   * This method returns the height of the tallest label in the slider's label
   * table.
   *
   * @return The height of the tallest label or 0 if no label table exists.
   */
01251   protected int getHeightOfTallestLabel()
  {
    int tallest = 0;
    Component label;

    if (slider.getLabelTable() == null)
      return 0;
    Dimension pref;
    for (Enumeration list = slider.getLabelTable().elements();
         list.hasMoreElements();)
      {
      Object comp = list.nextElement();
      if (! (comp instanceof Component))
        continue;
      label = (Component) comp;
      pref = label.getPreferredSize();
      if (pref != null && pref.height > tallest)
        tallest = pref.height;
      }
    return tallest;
  }

  /**
   * This method returns the width of the label whose key has the highest
   * value.
   *
   * @return The width of the high value label or 0 if no label table exists.
   */
01279   protected int getWidthOfHighValueLabel()
  {
    Component highValueLabel = getHighestValueLabel();
    if (highValueLabel != null)
      return highValueLabel.getWidth();
    else
      return 0;
  }

  /**
   * This method returns the width of the label whose key has the lowest
   * value.
   *
   * @return The width of the low value label or 0 if no label table exists.
   */
01294   protected int getWidthOfLowValueLabel()
  {
    Component lowValueLabel = getLowestValueLabel();
    if (lowValueLabel != null)
      return lowValueLabel.getWidth();
    else
      return 0;
  }

  /**
   * This method returns the height of the label whose key has the highest
   * value.
   *
   * @return The height of the high value label or 0 if no label table exists.
   */
01309   protected int getHeightOfHighValueLabel()
  {
    Component highValueLabel = getHighestValueLabel();
    if (highValueLabel != null)
      return highValueLabel.getHeight();
    else
      return 0;
  }

  /**
   * This method returns the height of the label whose key has the lowest
   * value.
   *
   * @return The height of the low value label or 0 if no label table exists.
   */
01324   protected int getHeightOfLowValueLabel()
  {
    Component lowValueLabel = getLowestValueLabel();
    if (lowValueLabel != null)
      return lowValueLabel.getHeight();
    else
      return 0;
  }

  /**
   * This method returns whether the slider is to be drawn inverted.
   *
   * @return True is the slider is to be drawn inverted.
   */
01338   protected boolean drawInverted()
  {
    return ! (slider.getInverted() ^ leftToRightCache);
  }

  /**
   * This method returns the label whose key has the lowest value.
   *
   * @return The low value label or null if no label table exists.
   */
01348   protected Component getLowestValueLabel()
  {
    Integer key = new Integer(Integer.MAX_VALUE);
    Integer tmpKey;
    Dictionary labelTable = slider.getLabelTable();

    if (labelTable == null)
      return null;

    for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
      {
      Object value = list.nextElement();
      if (! (value instanceof Integer))
        continue;
      tmpKey = (Integer) value;
      if (tmpKey.intValue() < key.intValue())
        key = tmpKey;
      }
    Object comp = labelTable.get(key);
    if (! (comp instanceof Component))
      return null;
    return (Component) comp;
  }

  /**
   * This method returns the label whose  key has the highest value.
   *
   * @return The high value label or null if no label table exists.
   */
01377   protected Component getHighestValueLabel()
  {
    Integer key = new Integer(Integer.MIN_VALUE);
    Integer tmpKey;
    Dictionary labelTable = slider.getLabelTable();

    if (labelTable == null)
      return null;

    for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
      {
      Object value = list.nextElement();
      if (! (value instanceof Integer))
        continue;
      tmpKey = (Integer) value;
      if (tmpKey.intValue() > key.intValue())
        key = tmpKey;
      }
    Object comp = labelTable.get(key);
    if (! (comp instanceof Component))
      return null;
    return (Component) comp;
  }

  /**
   * This method is used to paint the {@link JSlider}. It delegates all its
   * duties to the various paint methods like paintTicks(),  paintTrack(),
   * paintThumb(), etc.
   *
   * @param g The {@link Graphics} object to paint with.
   * @param c The {@link JComponent} that is being painted.
   */
01409   public void paint(Graphics g, JComponent c)
  {
    // FIXME: Move this to propertyChangeEvent handler, when we get those.
    leftToRightCache = slider.getComponentOrientation() != ComponentOrientation.RIGHT_TO_LEFT;
    // FIXME: This next line is only here because the above line is here.
    calculateThumbLocation();

    if (slider.getPaintTrack())
      paintTrack(g);
    if (slider.getPaintTicks())
      paintTicks(g);
    if (slider.getPaintLabels())
      paintLabels(g);

    //FIXME: Paint focus.
    paintThumb(g);
  }

  /**
   * This method recalculates any rectangles that need to be recalculated
   * after the insets of the component have changed.
   */
01431   protected void recalculateIfInsetsChanged()
  {
    // Examining a test program shows that either Sun calls private
    // methods that we don't know about, or these don't do anything.
    calculateFocusRect();

    calculateContentRect();
    calculateThumbSize();
    calculateTrackBuffer();
    calculateTrackRect();
    calculateThumbLocation();

    calculateTickRect();
    calculateLabelRect();
  }

  /**
   * This method recalculates any rectangles that need to be recalculated
   * after the orientation of the slider changes.
   */
01451   protected void recalculateIfOrientationChanged()
  {
    // Examining a test program shows that either Sun calls private
    // methods that we don't know about, or these don't do anything.  
    calculateThumbSize();
    calculateTrackBuffer();
    calculateTrackRect();
    calculateThumbLocation();

    calculateTickRect();
    calculateLabelRect();
  }

  /**
   * This method is called during a repaint if the slider has focus. It draws
   * an outline of the  focusRect using the color returned by
   * getFocusColor().
   *
   * @param g The {@link Graphics} object to draw with.
   */
01471   public void paintFocus(Graphics g)
  {
    Color saved_color = g.getColor();

    g.setColor(getFocusColor());

    g.drawRect(focusRect.x, focusRect.y, focusRect.width, focusRect.height);

    g.setColor(saved_color);
  }

  /**
   * <p>
   * This method is called during a repaint if the  track is to be drawn. It
   * draws a 3D rectangle to  represent the track. The track is not the size
   * of the trackRect. The top and left edges of the track should be outlined
   * with the shadow color. The bottom and right edges should be outlined
   * with the highlight color.
   * </p>
   * <pre>
   *    a---d   
   *    |   |   
   *    |   |   a------------------------d
   *    |   |   |                        |
   *    |   |   b------------------------c
   *    |   |
   *    |   |   
   *    b---c
   * </pre>
   * 
   * <p>
   * The b-a-d path needs to be drawn with the shadow color and the b-c-d path
   * needs to be drawn with the highlight color.
   * </p>
   *
   * @param g The {@link Graphics} object to draw with.
   */
01508   public void paintTrack(Graphics g)
  {
    Color saved_color = g.getColor();
    int width;
    int height;

    Point a = new Point(trackRect.x, trackRect.y);
    Point b = new Point(a);
    Point c = new Point(a);
    Point d = new Point(a);

    Polygon high;
    Polygon shadow;

    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      width = trackRect.width;
      height = (thumbRect.height / 4 == 0) ? 1 : thumbRect.height / 4;

      a.translate(0, (trackRect.height / 2) - (height / 2));
      b.translate(0, (trackRect.height / 2) + (height / 2));
      c.translate(trackRect.width, (trackRect.height / 2) + (height / 2));
      d.translate(trackRect.width, (trackRect.height / 2) - (height / 2));
      }
    else
      {
      width = (thumbRect.width / 4 == 0) ? 1 : thumbRect.width / 4;
      height = trackRect.height;

      a.translate((trackRect.width / 2) - (width / 2), 0);
      b.translate((trackRect.width / 2) - (width / 2), trackRect.height);
      c.translate((trackRect.width / 2) + (width / 2), trackRect.height);
      d.translate((trackRect.width / 2) + (width / 2), 0);
      }
    g.setColor(Color.GRAY);
    g.fillRect(a.x, a.y, width, height);

    g.setColor(getHighlightColor());
    g.drawLine(b.x, b.y, c.x, c.y);
    g.drawLine(c.x, c.y, d.x, d.y);

    g.setColor(getShadowColor());
    g.drawLine(b.x, b.y, a.x, a.y);
    g.drawLine(a.x, a.y, d.x, d.y);

    g.setColor(saved_color);
  }

  /**
   * This method is called during a repaint if the ticks are to be drawn. This
   * method must still verify that the majorTickSpacing and minorTickSpacing
   * are greater than zero before drawing the ticks.
   *
   * @param g The {@link Graphics} object to draw with.
   */
01563   public void paintTicks(Graphics g)
  {
    int max = slider.getMaximum();
    int min = slider.getMinimum();
    int majorSpace = slider.getMajorTickSpacing();
    int minorSpace = slider.getMinorTickSpacing();

    if (majorSpace > 0)
      {
      if (slider.getOrientation() == JSlider.HORIZONTAL)
        {
          double loc = tickRect.x;
          double increment = (max == min) ? 0
                                          : majorSpace * (double) tickRect.width / (max
                                          - min);
          if (drawInverted())
            {
            loc += tickRect.width;
            increment *= -1;
            }
          for (int i = min; i <= max; i += majorSpace)
            {
            paintMajorTickForHorizSlider(g, tickRect, (int) loc);
            loc += increment;
            }
        }
      else
        {
          double loc = tickRect.height + tickRect.y;
          double increment = (max == min) ? 0
                                          : -majorSpace * (double) tickRect.height / (max
                                          - min);
          if (drawInverted())
            {
            loc = tickRect.y;
            increment *= -1;
            }
          for (int i = min; i <= max; i += majorSpace)
            {
            paintMajorTickForVertSlider(g, tickRect, (int) loc);
            loc += increment;
            }
        }
      }
    if (minorSpace > 0)
      {
      if (slider.getOrientation() == JSlider.HORIZONTAL)
        {
          double loc = tickRect.x;
          double increment = (max == min) ? 0
                                          : minorSpace * (double) tickRect.width / (max
                                          - min);
          if (drawInverted())
            {
            loc += tickRect.width;
            increment *= -1;
            }
          for (int i = min; i <= max; i += minorSpace)
            {
            paintMinorTickForHorizSlider(g, tickRect, (int) loc);
            loc += increment;
            }
        }
      else
        {
          double loc = tickRect.height + tickRect.y;
          double increment = (max == min) ? 0
                                          : -minorSpace * (double) tickRect.height / (max
                                          - min);
          if (drawInverted())
            {
            loc = tickRect.y;
            increment *= -1;
            }
          for (int i = min; i <= max; i += minorSpace)
            {
            paintMinorTickForVertSlider(g, tickRect, (int) loc);
            loc += increment;
            }
        }
      }
  }

  /* Minor ticks start at 1/4 of the height (or width) of the tickRect and extend
     to 1/2 of the tickRect.

     Major ticks start at 1/4 of the height and extend to 3/4.
   */

  /**
   * This method paints a minor tick for a horizontal slider at the given x
   * value. x represents the x coordinate to paint at.
   *
   * @param g The {@link Graphics} object to draw with.
   * @param tickBounds The tickRect rectangle.
   * @param x The x coordinate to draw the tick at.
   */
01660   protected void paintMinorTickForHorizSlider(Graphics g,
                                              Rectangle tickBounds, int x)
  {
    int y = tickRect.y + tickRect.height / 4;
    Color saved = g.getColor();
    g.setColor(Color.BLACK);

    g.drawLine(x, y, x, y + tickRect.height / 4);
    g.setColor(saved);
  }

  /**
   * This method paints a major tick for a horizontal slider at the given x
   * value. x represents the x coordinate to paint at.
   *
   * @param g The {@link Graphics} object to draw with.
   * @param tickBounds The tickRect rectangle.
   * @param x The x coordinate to draw the tick at.
   */
01679   protected void paintMajorTickForHorizSlider(Graphics g,
                                              Rectangle tickBounds, int x)
  {
    int y = tickRect.y + tickRect.height / 4;
    Color saved = g.getColor();
    g.setColor(Color.BLACK);

    g.drawLine(x, y, x, y + tickRect.height / 2);
    g.setColor(saved);
  }

  /**
   * This method paints a minor tick for a vertical slider at the given y
   * value. y represents the y coordinate to paint at.
   *
   * @param g The {@link Graphics} object to draw with.
   * @param tickBounds The tickRect rectangle.
   * @param y The y coordinate to draw the tick at.
   */
01698   protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds,
                                             int y)
  {
    int x = tickRect.x + tickRect.width / 4;
    Color saved = g.getColor();
    g.setColor(Color.BLACK);

    g.drawLine(x, y, x + tickRect.width / 4, y);
    g.setColor(saved);
  }

  /**
   * This method paints a major tick for a vertical slider at the given y
   * value. y represents the y coordinate to paint at.
   *
   * @param g The {@link Graphics} object to draw with.
   * @param tickBounds The tickRect rectangle.
   * @param y The y coordinate to draw the tick at.
   */
01717   protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds,
                                             int y)
  {
    int x = tickRect.x + tickRect.width / 4;
    Color saved = g.getColor();
    g.setColor(Color.BLACK);

    g.drawLine(x, y, x + tickRect.width / 2, y);
    g.setColor(saved);
  }

  /**
   * This method paints all the labels from the slider's label table. This
   * method must make sure that the label table is not null before painting
   * the labels. Each entry in the label table is a (integer, component)
   * pair. Every label is painted at the value of the integer.
   *
   * @param g The {@link Graphics} object to draw with.
   */
01736   public void paintLabels(Graphics g)
  {
    if (slider.getLabelTable() != null)
      {
      Dictionary table = slider.getLabelTable();
      Integer tmpKey;
      Object key;
      Object element;
      Component label;
      if (slider.getOrientation() == JSlider.HORIZONTAL)
        {
          for (Enumeration list = table.keys(); list.hasMoreElements();)
            {
            key = list.nextElement();
            if (! (key instanceof Integer))
              continue;
            tmpKey = (Integer) key;
            element = table.get(tmpKey);
            // We won't paint them if they're not
            // JLabels so continue anyway
            if (! (element instanceof JLabel))
              continue;
            label = (Component) element;
            paintHorizontalLabel(g, tmpKey.intValue(), label);
            }
        }
      else
        {
          for (Enumeration list = table.keys(); list.hasMoreElements();)
            {
            key = list.nextElement();
            if (! (key instanceof Integer))
              continue;
            tmpKey = (Integer) key;
            element = table.get(tmpKey);
            // We won't paint them if they're not
            // JLabels so continue anyway
            if (! (element instanceof JLabel))
              continue;
            label = (Component) element;
            paintVerticalLabel(g, tmpKey.intValue(), label);
            }
        }
      }
  }

  /**
   * This method paints the label on the horizontal slider at the value
   * specified. The value is not a coordinate. It is a value within the range
   * of the  slider. If the value is not within the range of the slider, this
   * method will do nothing. This method should not paint outside the
   * boundaries of the labelRect.
   *
   * @param g The {@link Graphics} object to draw with.
   * @param value The value to paint at.
   * @param label The label to paint.
   */
01793   protected void paintHorizontalLabel(Graphics g, int value, Component label)
  {
    // This relies on clipping working properly or we'll end up
    // painting all over the place. If our preferred size is ignored, then
    // the labels may not fit inside the slider's bounds. Rather than mucking 
    // with font sizes and possible icon sizes, we'll set the bounds for
    // the label and let it get clipped.
    Dimension dim = label.getPreferredSize();
    int w = (int) dim.getWidth();
    int h = (int) dim.getHeight();

    int max = slider.getMaximum();
    int min = slider.getMinimum();

    if (value > max || value < min)
      return;

    //           value
    //             |
    //        ------------
    //        |          |
    //        |          |
    //        |          |
    //  The label must move w/2 to the right to fit directly under the value.
    int xpos = xPositionForValue(value) - w / 2;
    int ypos = labelRect.y;

    // We want to center the label around the xPositionForValue
    // So we use xpos - w / 2. However, if value is min and the label 
    // is large, we run the risk of going out of bounds. So we bring it back
    // to 0 if it becomes negative.
    if (xpos < 0)
      xpos = 0;

    // If the label + starting x position is greater than
    // the x space in the label rectangle, we reset it to the largest
    // amount possible in the rectangle. This means ugliness.
    if (xpos + w > labelRect.x + labelRect.width)
      w = labelRect.x + labelRect.width - xpos;

    // If the label is too tall. We reset it to the height of the label
    // rectangle.
    if (h > labelRect.height)
      h = labelRect.height;

    label.setBounds(xpos, ypos, w, h);
    javax.swing.SwingUtilities.paintComponent(g, label, null, label.getBounds());
  }

  /**
   * This method paints the label on the vertical slider at the value
   * specified. The value is not a coordinate. It is a value within the range
   * of the  slider. If the value is not within the range of the slider, this
   * method will do nothing. This method should not paint outside the
   * boundaries of the labelRect.
   *
   * @param g The {@link Graphics} object to draw with.
   * @param value The value to paint at.
   * @param label The label to paint.
   */
01853   protected void paintVerticalLabel(Graphics g, int value, Component label)
  {
    Dimension dim = label.getPreferredSize();
    int w = (int) dim.getWidth();
    int h = (int) dim.getHeight();

    int max = slider.getMaximum();
    int min = slider.getMinimum();

    if (value > max || value < min)
      return;

    int xpos = labelRect.x;
    int ypos = yPositionForValue(value) - h / 2;

    if (ypos < 0)
      ypos = 0;

    if (ypos + h > labelRect.y + labelRect.height)
      h = labelRect.y + labelRect.height - ypos;

    if (w > labelRect.width)
      w = labelRect.width;

    label.setBounds(xpos, ypos, w, h);
    javax.swing.SwingUtilities.paintComponent(g, label, null, label.getBounds());
  }

  /**
   * <p>
   * This method paints a thumb. There are two types of thumb:
   * </p>
   * <pre>
   *   Vertical         Horizontal
   *    a---b            a-----b
   *    |   |            |      \
   *    e   c            |       c
   *     \ /             |      /
   *      d              e-----d
   *  </pre>
   * 
   * <p>
   * In the case of vertical thumbs, we highlight the path b-a-e-d and shadow
   * the path b-c-d. In the case of horizontal thumbs, we highlight the path
   * c-b-a-e and shadow the path c-d-e. In both cases we fill the path
   * a-b-c-d-e before shadows and highlights are drawn.
   * </p>
   *
   * @param g The graphics object to paint with
   */
01903   public void paintThumb(Graphics g)
  {
    Color saved_color = g.getColor();

    Polygon thumb = new Polygon();

    Point a = new Point(thumbRect.x, thumbRect.y);
    Point b = new Point(a);
    Point c = new Point(a);
    Point d = new Point(a);
    Point e = new Point(a);

    Polygon bright;
    Polygon dark;
    Polygon all;

    // This will be in X-dimension if the slider is inverted and y if it isn't.             
    int turnPoint;

    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      turnPoint = thumbRect.height * 3 / 4;

      b.translate(thumbRect.width, 0);
      c.translate(thumbRect.width, turnPoint);
      d.translate(thumbRect.width / 2, thumbRect.height);
      e.translate(0, turnPoint);

      bright = new Polygon(new int[] { b.x, a.x, e.x, d.x },
                           new int[] { b.y, a.y, e.y, d.y }, 4);

      dark = new Polygon(new int[] { b.x, c.x, d.x },
                         new int[] { b.y, c.y, d.y }, 3);
      all = new Polygon(new int[] { a.x + 1, b.x, c.x, d.x, e.x + 1 },
                        new int[] { a.y + 1, b.y + 1, c.y, d.y + 1, e.y }, 5);
      }
    else
      {
      turnPoint = thumbRect.width * 3 / 4;

      b.translate(turnPoint, 0);
      c.translate(thumbRect.width, thumbRect.height / 2);
      d.translate(turnPoint, thumbRect.height);
      e.translate(0, thumbRect.height);

      bright = new Polygon(new int[] { c.x, b.x, a.x, e.x },
                           new int[] { c.y, b.y, a.y, e.y }, 4);

      dark = new Polygon(new int[] { c.x, d.x, e.x + 1 },
                         new int[] { c.y, d.y, e.y }, 3);

      all = new Polygon(new int[] { a.x + 1, b.x, c.x - 1, d.x, e.x + 1 },
                        new int[] { a.y + 1, b.y + 1, c.y, d.y, e.y }, 5);
      }

    g.setColor(Color.WHITE);
    g.drawPolyline(bright.xpoints, bright.ypoints, bright.npoints);

    g.setColor(Color.BLACK);
    g.drawPolyline(dark.xpoints, dark.ypoints, dark.npoints);

    g.setColor(Color.GRAY);
    g.fillPolygon(all);

    g.setColor(saved_color);
  }

  /**
   * This method sets the position of the thumbRect.
   *
   * @param x The new x position.
   * @param y The new y position.
   */
01976   public void setThumbLocation(int x, int y)
  {
    thumbRect.x = x;
    thumbRect.y = y;
  }

  /**
   * This method is used to move the thumb one  block in the direction
   * specified. If the slider  snaps to ticks, this method is responsible for
   * snapping it to a tick after the thumb  has been moved.
   *
   * @param direction The direction to move in.
   */
01989   public void scrollByBlock(int direction)
  {
    // The direction is -1 for backwards and 1 for forwards.
    int unit = direction * (slider.getMaximum() - slider.getMinimum()) / 10;

    int moveTo = slider.getValue() + unit;

    if (slider.getSnapToTicks())
      moveTo = findClosestTick(moveTo);

    slider.setValue(moveTo);
  }

  /**
   * This method is used to move the thumb one unit in the direction
   * specified. If the slider snaps to ticks, this method is responsible for
   * snapping it to a tick after the thumb has been moved.
   *
   * @param direction The direction to move in.
   */
02009   public void scrollByUnit(int direction)
  {
    // The direction is -1 for backwards and 1 for forwards.
    int moveTo = slider.getValue() + direction;

    if (slider.getSnapToTicks())
      moveTo = findClosestTick(moveTo);

    slider.setValue(moveTo);
  }

  /**
   * This method is called when there has been a click in the track and the
   * thumb needs to be scrolled  on regular intervals. This method is only
   * responsible  for starting the timer and not for stopping it.
   *
   * @param dir The direction to move in.
   */
02027   protected void scrollDueToClickInTrack(int dir)
  {
    scrollTimer.stop();

    scrollListener.setDirection(dir);
    scrollListener.setScrollByBlock(true);

    scrollTimer.start();
  }

  /**
   * This method returns the X coordinate for the value passed in.
   *
   * @param value The value to calculate an x coordinate for.
   *
   * @return The x coordinate for the value.
   */
02044   protected int xPositionForValue(int value)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int extent = slider.getExtent();
    int len = trackRect.width;

    int xPos = (max == min) ? 0 : (value - min) * len / (max - min);

    if (! drawInverted())
      xPos += trackRect.x;
    else
      {
      xPos = trackRect.width - xPos;
      xPos += trackRect.x;
      }
    return xPos;
  }

  /**
   * This method returns the y coordinate for the value passed in.
   *
   * @param value The value to calculate a y coordinate for.
   *
   * @return The y coordinate for the value.
   */
02070   protected int yPositionForValue(int value)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int extent = slider.getExtent();
    int len = trackRect.height;

    int yPos = (max == min) ? 0 : (value - min) * len / (max - min);

    if (! drawInverted())
      {
      yPos = trackRect.height - yPos;
      yPos += trackRect.y;
      }
    else
      yPos += trackRect.y;
    return yPos;
  }

  /**
   * This method returns the value in the slider's range given the y
   * coordinate. If the value is out of range, it will  return the closest
   * legal value.
   *
   * @param yPos The y coordinate to calculate a value for.
   *
   * @return The value for the y coordinate.
   */
02098   public int valueForYPosition(int yPos)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int len = trackRect.height;

    int value;

    // If the length is 0, you shouldn't be able to even see where the slider is.
    // This really shouldn't ever happen, but just in case, we'll return the middle.
    if (len == 0)
      return ((max - min) / 2);

    if (! drawInverted())
      value = ((len - (yPos - trackRect.y)) * (max - min) / len + min);
    else
      value = ((yPos - trackRect.y) * (max - min) / len + min);

    // If this isn't a legal value, then we'll have to move to one now.
    if (value > max)
      value = max;
    else if (value < min)
      value = min;
    return value;
  }

  /**
   * This method returns the value in the slider's range given the x
   * coordinate. If the value is out of range, it will return the closest
   * legal value.
   *
   * @param xPos The x coordinate to calculate a value for.
   *
   * @return The value for the x coordinate.
   */
02133   public int valueForXPosition(int xPos)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int len = trackRect.width;

    int value;

    // If the length is 0, you shouldn't be able to even see where the slider is.
    // This really shouldn't ever happen, but just in case, we'll return the middle.
    if (len == 0)
      return ((max - min) / 2);

    if (! drawInverted())
      value = ((xPos - trackRect.x) * (max - min) / len + min);
    else
      value = ((len - (xPos - trackRect.x)) * (max - min) / len + min);

    // If this isn't a legal value, then we'll have to move to one now.
    if (value > max)
      value = max;
    else if (value < min)
      value = min;
    return value;
  }

  /**
   * This method finds the closest value that has a tick associated with it.
   *
   * @param value The value to search from.
   *
   * @return The closest value that has a tick associated with it.
   */
02166   private int findClosestTick(int value)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int majorSpace = slider.getMajorTickSpacing();
    int minorSpace = slider.getMinorTickSpacing();

    // The default value to return is value + minor or
    // value + major. 
    // Initializing at min - value leaves us with a default
    // return value of min, which always has tick marks
    // (if ticks are painted).
    int minor = min - value;
    int major = min - value;

    // If there are no major tick marks or minor tick marks 
    // e.g. snap is set to true but no ticks are set, then
    // we can just return the value.
    if (majorSpace <= 0 && minorSpace <= 0)
      return value;

    // First check the major ticks.
    if (majorSpace > 0)
      {
      int lowerBound = (value - min) / majorSpace;
      int majLower = majorSpace * lowerBound + min;
      int majHigher = majorSpace * (lowerBound + 1) + min;

      if (majHigher <= max && majHigher - value <= value - majLower)
        major = majHigher - value;
      else
        major = majLower - value;
      }

    if (minorSpace > 0)
      {
      int lowerBound = value / minorSpace;
      int minLower = minorSpace * lowerBound;
      int minHigher = minorSpace * (lowerBound + 1);

      if (minHigher <= max && minHigher - value <= value - minLower)
        minor = minHigher - value;
      else
        minor = minLower - value;
      }

    // Give preference to minor ticks
    if (Math.abs(minor) > Math.abs(major))
      return value + major;
    else
      return value + minor;
  }
}

Generated by  Doxygen 1.6.0   Back to index