Logo Search packages:      
Sourcecode: kaffe version File versions  Download package

BasicSliderUI.java

/* BasicSliderUI.java --
   Copyright (C) 2004, 2005  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., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 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.AbstractAction;
import javax.swing.BoundedRangeModel;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
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>
 */
00138 public class BasicSliderUI extends SliderUI
{
  /**
   * Helper class that listens to the {@link JSlider}'s model for changes.
   *
   * @specnote Apparently this class was intended to be protected,
   *           but was made public by a compiler bug and is now
   *           public for compatibility.
   */
00147   public 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.
     */
00156     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.
   *
   * @specnote Apparently this class was intended to be protected,
   *           but was made public by a compiler bug and is now
   *           public for compatibility.
   */
00173   public 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}.
     */
00182     public void componentResized(ComponentEvent e)
    {
      calculateGeometry();

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

  /**
   * Helper class that listens for focus events.
   *
   * @specnote Apparently this class was intended to be protected,
   *           but was made public by a compiler bug and is now
   *           public for compatibility.
   */
00198   public 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}.
     */
00206     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}.
     */
00217     public void focusLost(FocusEvent e)
    {
      // FIXME: implement.
    }
  }

  /**
   * Helper class that listens for changes to the properties of the {@link
   * JSlider}.
   */
00227   public 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}.
     */
00235     public void propertyChange(PropertyChangeEvent e)
    {
      // Check for orientation changes.
      if (e.getPropertyName().equals("orientation"))
      recalculateIfOrientationChanged();
      else if (e.getPropertyName().equals("model"))
        {
        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.
   *
   * @specnote Apparently this class was intended to be protected,
   *           but was made public by a compiler bug and is now
   *           public for compatibility.
   */
00267   public class ScrollListener implements ActionListener
  {
    /** Indicates which direction the thumb should scroll. */
00270     private transient int direction;

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

    /**
     * Creates a new ScrollListener object.
     */
00278     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.
     */
00290     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}.
     */
00303     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.
     */
00322     public void setDirection(int direction)
    {
      this.direction = direction;
    }

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

  /**
   * Helper class that listens for mouse events.
   *
   * @specnote Apparently this class was intended to be protected,
   *           but was made public by a compiler bug and is now
   *           public for compatibility.
   */
00345   public class TrackListener extends MouseInputAdapter
  {
    /** The current X position of the mouse. */
00348     protected int currentMouseX;

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

    /**
     * The offset between the current slider value and the cursor's position.
     */
00356     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}
     */
00365     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}
     */
00387     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}
     */
00400     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}
     */
00437     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.
     */
00459     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());
    }
  }

  /**
   * This class is no longer used as of JDK1.3.
   */
00477   public class ActionScroller extends AbstractAction
  {
    /**
     * Not used.
     *
     * @param slider not used
     * @param dir not used
     * @param block not used
     */
00486     public ActionScroller(JSlider slider, int dir, boolean block)
    {
      // Not used.
    }

    /**
     * Not used.
     *
     * @param event not used
     */
00496     public void actionPerformed(ActionEvent event)
    {
      // Not used.
    }
  }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  /**
   * Creates a new Basic look and feel Slider UI.
   *
   * @param b The {@link JSlider} that this UI was created for.
   */
00582   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.
   */
00593   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.
   */
00604   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.
   */
00616   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}.
   */
00629   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.
   */
00641   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.
   */
00685   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.
   */
00710   protected void installDefaults(JSlider slider)
  {
    LookAndFeel.installColors(slider, "Slider.background",
                              "Slider.foreground");
    LookAndFeel.installBorder(slider, "Slider.border");
    shadowColor = UIManager.getColor("Slider.shadow");
    highlightColor = UIManager.getColor("Slider.highlight");
    focusColor = UIManager.getColor("Slider.focus");
    focusInsets = UIManager.getInsets("Slider.focusInsets");
    slider.setOpaque(true);
  }

  /**
   * Creates a new {@link TrackListener}.
   *
   * @param slider The {@link JSlider} that this {@link TrackListener} is
   *        created for.
   *
   * @return A new {@link TrackListener}.
   */
00730   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}.
   */
00743   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}.
   */
00756   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}.
   */
00769   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}.
   */
00782   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}.
   */
00795   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.
   */
00806   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.
   */
00831   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.
   */
00857   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.
   */
00869   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.
   */
00895   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 = getThumbSize().height;

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

    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.
   */
00936   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 = getThumbSize().width;

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

    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.
   */
00970   public Dimension getMinimumHorizontalSize()
  {
    Insets insets = slider.getInsets();
    // Height is determined by the thumb, the ticks and the labels.
    int height = getThumbSize().height; 

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

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

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

    return new Dimension(36, height);
  }

  /**
   * This method returns the minimum size of the slider when it  is vertically
   * oriented.
   *
   * @return The dimensions of the minimum vertical size.
   */
00995   public Dimension getMinimumVerticalSize()
  {
    Insets insets = slider.getInsets();
    int width = getThumbSize().width;

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

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

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

    return new Dimension(width, 36);
  }

  /**
   * 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.
   */
01021   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.
   */
01038   public Dimension getMinimumSize(JComponent c)
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      return getMinimumHorizontalSize();
    else
      return getMinimumVerticalSize();
  }

  /**
   * This method returns the maximum size for this {@link JSlider} for this
   * look and feel.
   *
   * @param c The {@link JComponent} to find a maximum size for.
   *
   * @return The dimensions of the maximum size.
   */
01054   public Dimension getMaximumSize(JComponent c)
  {
    Insets insets = slider.getInsets();
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
        // Height is determined by the thumb, the ticks and the labels.
        int height = getThumbSize().height; 

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

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

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

        return new Dimension(32767, height);
      }
    else
      {
        int width = getThumbSize().width;

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

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

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

        return new Dimension(width, 32767);
      }
  }

  /**
   * This method calculates all the sizes of the rectangles by delegating to
   * the helper methods calculateXXXRect.
   */
01096   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.
   */
01112   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.
   */
01126   protected void calculateThumbSize()
  {
    Dimension d = getThumbSize();
    thumbRect.width = d.width;
    thumbRect.height = d.height;
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      thumbRect.y = trackRect.y;
    else
      thumbRect.x = trackRect.x;
  }

  /**
   * This method calculates the size and position of the contentRect. This
   * method does not need to be  called if the orientation changes.
   */
01141   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.
   */
01160   protected void calculateThumbLocation()
  {
    int value = slider.getValue();

    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      thumbRect.x = xPositionForValue(value) - thumbRect.width / 2;
      thumbRect.y = trackRect.y;
      }
    else
      {
      thumbRect.x = trackRect.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.
   */
01180   protected void calculateTrackBuffer()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      trackBuffer = thumbRect.width / 2;
    else
      trackBuffer = thumbRect.height / 2;
  }

  /**
   * This method returns the size of the thumbRect.
   *
   * @return The dimensions of the thumb.
   */
01193   protected Dimension getThumbSize()
  {
    // TODO: shouldn't create new objects every time
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      return new Dimension(11, 20);
    else
      return new Dimension(20, 11);
  }

  /**
   * Calculates the size and position of the trackRect. It must take into
   * account the orientation of the slider.
   */
01206   protected void calculateTrackRect()
  {
    if (slider.getOrientation() == JSlider.HORIZONTAL)
      {
      trackRect.x = contentRect.x + trackBuffer;
        int h = getThumbSize().height;
        if (slider.getPaintTicks() && (slider.getMajorTickSpacing() > 0 
            || slider.getMinorTickSpacing() > 0))
          h += getTickLength();
      trackRect.y = contentRect.y + (contentRect.height - h) / 2 - 1;
      trackRect.width = contentRect.width - 2 * trackBuffer;
      trackRect.height = thumbRect.height;
      }
    else
      {
        int w = getThumbSize().width;
        if (slider.getPaintTicks() && (slider.getMajorTickSpacing() > 0
            || slider.getMinorTickSpacing() > 0))
          w += getTickLength();  
      trackRect.x = contentRect.x + (contentRect.width - w) / 2 - 1;
      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.
   */
01242   protected int getTickLength()
  {
    return 8;
  }

  /**
   * This method calculates the size and position of the tickRect. It must
   * take into account the orientation of the slider.
   */
01251   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.
   */
01279   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.
   */
01303   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.
   */
01332   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.
   */
01360   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.
   */
01375   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.
   */
01390   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.
   */
01405   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.
   */
01419   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.
   */
01429   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.
   */
01458   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.
   */
01490   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.
    calculateGeometry();

    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.
   */
01512   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.
   */
01532   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.
   */
01552   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.
   */
01589   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);

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

  /* 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.
   */
01742   protected void paintMinorTickForHorizSlider(Graphics g,
                                              Rectangle tickBounds, int x)
  {
    int 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.
   */
01761   protected void paintMajorTickForHorizSlider(Graphics g,
                                              Rectangle tickBounds, int x)
  {
    int 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.
   */
01780   protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds,
                                             int y)
  {
    int 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.
   */
01799   protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds,
                                             int y)
  {
    int 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.
   */
01818   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.
   */
01875   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.
   */
01935   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
   */
01985   public void paintThumb(Graphics g)
  {
    Color saved_color = g.getColor();
    
    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 light;  // light shadow
    Polygon dark;   // dark shadow
    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 - 1, 0);
      c.translate(thumbRect.width - 1, turnPoint);
      d.translate(thumbRect.width / 2 - 1, thumbRect.height - 1);
      e.translate(0, turnPoint);

      bright = new Polygon(new int[] { b.x - 1, 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 + 1 },
                         new int[] { b.y, c.y - 1, d.y }, 3);
    
    light = new Polygon(new int[] { b.x - 1, c.x - 1, d.x + 1 },
                        new int[] { b.y + 1, c.y - 1, d.y - 1 }, 3);
    
      all = new Polygon(new int[] { a.x + 1, b.x - 2, c.x - 2, d.x, e.x + 1 },
                        new int[] { a.y + 1, b.y + 1, c.y - 1, d.y - 1, e.y }, 5);
      }
    else
      {
      turnPoint = thumbRect.width * 3 / 4 - 1;

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

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

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

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

    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.drawPolyline(light.xpoints, light.ypoints, light.npoints);
    
    g.setColor(Color.LIGHT_GRAY);
    g.drawPolyline(all.xpoints, all.ypoints, all.npoints);
    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.
   */
02067   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.
   */
02080   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.
   */
02100   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.
   */
02118   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.
   */
02135   protected int xPositionForValue(int value)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int len = trackRect.width - 1;

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

    if (! drawInverted())
      xPos += trackRect.x;
    else
      {
      xPos = len - 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.
   */
02160   protected int yPositionForValue(int value)
  {
    int min = slider.getMinimum();
    int max = slider.getMaximum();
    int len = trackRect.height - 1;

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

    if (! drawInverted())
      {
      yPos = len - 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.
   */
02187   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.
   */
02223   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.
   * This is package-private to avoid an accessor method.
   *
   * @param value The value to search from.
   *
   * @return The closest value that has a tick associated with it.
   */
02258   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