001/* BasicSliderUI.java --
002   Copyright (C) 2004, 2005, 2006,  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.Color;
042import java.awt.Component;
043import java.awt.Dimension;
044import java.awt.Graphics;
045import java.awt.Insets;
046import java.awt.Point;
047import java.awt.Polygon;
048import java.awt.Rectangle;
049import java.awt.event.ActionEvent;
050import java.awt.event.ActionListener;
051import java.awt.event.ComponentAdapter;
052import java.awt.event.ComponentEvent;
053import java.awt.event.ComponentListener;
054import java.awt.event.FocusEvent;
055import java.awt.event.FocusListener;
056import java.awt.event.MouseEvent;
057import java.beans.PropertyChangeEvent;
058import java.beans.PropertyChangeListener;
059import java.util.Dictionary;
060import java.util.Enumeration;
061
062import javax.swing.AbstractAction;
063import javax.swing.ActionMap;
064import javax.swing.BoundedRangeModel;
065import javax.swing.InputMap;
066import javax.swing.JComponent;
067import javax.swing.JSlider;
068import javax.swing.LookAndFeel;
069import javax.swing.SwingUtilities;
070import javax.swing.Timer;
071import javax.swing.UIManager;
072import javax.swing.event.ChangeEvent;
073import javax.swing.event.ChangeListener;
074import javax.swing.event.MouseInputAdapter;
075import javax.swing.plaf.ActionMapUIResource;
076import javax.swing.plaf.ComponentUI;
077import javax.swing.plaf.SliderUI;
078
079/**
080 * <p>
081 * BasicSliderUI.java This is the UI delegate in the Basic look and feel that
082 * paints JSliders.
083 * </p>
084 * 
085 * <p>
086 * The UI delegate keeps track of 6 rectangles that place the various parts of
087 * the JSlider inside the component.
088 * </p>
089 * 
090 * <p>
091 * The rectangles are organized as follows:
092 * </p>
093 * <pre>
094 *     +-------------------------------------------------------+ <-- focusRect
095 *     |                                                       |
096 *     |  +==+-------------------+==+--------------------+==+<------ contentRect
097 *     |  |  |                   |  |<---thumbRect       |  |  |
098 *     |  |  |    TRACK          |  |                    |<--------- trackRect
099 *     |  |  +-------------------+==+--------------------+  |  |
100 *     |  |  |                                           |  |  |
101 *     |  |  |          TICKS GO HERE                    |<-------- tickRect
102 *     |  |  |                                           |  |  |
103 *     |  +==+-------------------------------------------+==+  |
104 *     |  |  |                                           |  |  |
105 *     |  |  |                                           |  |<----- labelRect
106 *     |  |  |                 LABELS GO HERE            |  |  |
107 *     |  |  |                                           |  |  |
108 *     |  |  |                                           |  |  |
109 *     |  |  |                                           |  |  |
110 *     |  |  |                                           |  |  |
111 *     |  |                                              |  |  |
112 * </pre>
113 * 
114 * <p>
115 * The space between the contentRect and the focusRect are the FocusInsets.
116 * </p>
117 * 
118 * <p>
119 * The space between the focusRect and the component bounds is the insetCache
120 * which are the component's insets.
121 * </p>
122 * 
123 * <p>
124 * The top of the thumb is the top of the contentRect. The trackRect has to be
125 * as tall as the thumb.
126 * </p>
127 * 
128 * <p>
129 * The trackRect and tickRect do not start from the left edge of the
130 * focusRect. They are trackBuffer away from each side of the focusRect. This
131 * is so that the thumb has room to move.
132 * </p>
133 * 
134 * <p>
135 * The labelRect does start right against the contentRect's left and right
136 * edges and it gets all remaining space.
137 * </p>
138 */
139public class BasicSliderUI extends SliderUI
140{
141  /**
142   * Helper class that listens to the {@link JSlider}'s model for changes.
143   *
144   * @specnote Apparently this class was intended to be protected,
145   *           but was made public by a compiler bug and is now
146   *           public for compatibility.
147   */
148  public class ChangeHandler implements ChangeListener
149  {
150    /**
151     * Called when the slider's model has been altered. The UI delegate should
152     * recalculate any rectangles that are dependent on the model for their
153     * positions and repaint.
154     *
155     * @param e A static {@link ChangeEvent} passed from the model.
156     */
157    public void stateChanged(ChangeEvent e)
158    {
159      // Maximum, minimum, and extent values will be taken
160      // care of automatically when the slider is repainted.
161      // Only thing that needs recalculation is the thumb.
162      calculateThumbLocation();
163      slider.repaint();
164    }
165  }
166
167  /**
168   * Helper class that listens for resize events.
169   *
170   * @specnote Apparently this class was intended to be protected,
171   *           but was made public by a compiler bug and is now
172   *           public for compatibility.
173   */
174  public class ComponentHandler extends ComponentAdapter
175  {
176    /**
177     * Called when the size of the component changes. The UI delegate should
178     * recalculate any rectangles that are dependent on the model for their
179     * positions and repaint.
180     *
181     * @param e A {@link ComponentEvent}.
182     */
183    public void componentResized(ComponentEvent e)
184    {
185      calculateGeometry();
186      slider.repaint();
187    }
188  }
189
190  /**
191   * Helper class that listens for focus events.
192   *
193   * @specnote Apparently this class was intended to be protected,
194   *           but was made public by a compiler bug and is now
195   *           public for compatibility.
196   */
197  public class FocusHandler implements FocusListener
198  {
199    /**
200     * Called when the {@link JSlider} has gained focus.  It should repaint
201     * the slider with the focus drawn.
202     *
203     * @param e A {@link FocusEvent}.
204     */
205    public void focusGained(FocusEvent e)
206    {
207      slider.repaint();
208    }
209
210    /**
211     * Called when the {@link JSlider} has lost focus. It  should repaint the
212     * slider without the focus drawn.
213     *
214     * @param e A {@link FocusEvent}.
215     */
216    public void focusLost(FocusEvent e)
217    {
218      slider.repaint();
219    }
220  }
221
222  /**
223   * Helper class that listens for changes to the properties of the {@link
224   * JSlider}.
225   */
226  public class PropertyChangeHandler implements PropertyChangeListener
227  {
228    /**
229     * Called when one of the properties change. The UI should recalculate any
230     * rectangles if necessary and repaint.
231     *
232     * @param e A {@link PropertyChangeEvent}.
233     */
234    public void propertyChange(PropertyChangeEvent e)
235    {
236      // Check for orientation changes.
237      String prop = e.getPropertyName();
238      if (prop.equals("orientation")
239          || prop.equals("inverted")
240          || prop.equals("labelTable")
241          || prop.equals("majorTickSpacing")
242          || prop.equals("minorTickSpacing")
243          || prop.equals("paintTicks")
244          || prop.equals("paintTrack")
245          || prop.equals("paintLabels"))
246        {
247          calculateGeometry();
248          slider.repaint();
249        }
250      else if (e.getPropertyName().equals("model"))
251        {
252          BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue();
253          oldModel.removeChangeListener(changeListener);
254          slider.getModel().addChangeListener(changeListener);
255          calculateThumbLocation();
256          slider.repaint();
257        }
258    }
259  }
260
261  /**
262   * Helper class that listens to our swing timer. This class is responsible
263   * for listening to the timer and moving the thumb in the proper direction
264   * every interval.
265   *
266   * @specnote Apparently this class was intended to be protected,
267   *           but was made public by a compiler bug and is now
268   *           public for compatibility.
269   */
270  public class ScrollListener implements ActionListener
271  {
272    /** Indicates which direction the thumb should scroll. */
273    private transient int direction;
274
275    /** Indicates whether we should scroll in blocks or in units. */
276    private transient boolean block;
277
278    /**
279     * Creates a new ScrollListener object.
280     */
281    public ScrollListener()
282    {
283      direction = POSITIVE_SCROLL;
284      block = false;
285    }
286
287    /**
288     * Creates a new ScrollListener object.
289     *
290     * @param dir The direction to scroll in.
291     * @param block If movement will be in blocks.
292     */
293    public ScrollListener(int dir, boolean block)
294    {
295      direction = dir;
296      this.block = block;
297    }
298
299    /**
300     * Called every time the swing timer reaches its interval. If the thumb
301     * needs to move, then this method will move the thumb one block or  unit
302     * in the direction desired. Otherwise, the timer can be stopped.
303     *
304     * @param e An {@link ActionEvent}.
305     */
306    public void actionPerformed(ActionEvent e)
307    {
308      if (! trackListener.shouldScroll(direction))
309        {
310          scrollTimer.stop();
311          return;
312        }
313
314      if (block)
315        scrollByBlock(direction);
316      else
317        scrollByUnit(direction);
318    }
319
320    /**
321     * Sets the direction to scroll in.
322     *
323     * @param direction The direction to scroll in.
324     */
325    public void setDirection(int direction)
326    {
327      this.direction = direction;
328    }
329
330    /**
331     * Sets whether movement will be in blocks.
332     *
333     * @param block If movement will be in blocks.
334     */
335    public void setScrollByBlock(boolean block)
336    {
337      this.block = block;
338    }
339  }
340
341  /**
342   * Helper class that listens for mouse events.
343   *
344   * @specnote Apparently this class was intended to be protected,
345   *           but was made public by a compiler bug and is now
346   *           public for compatibility.
347   */
348  public class TrackListener extends MouseInputAdapter
349  {
350    /** The current X position of the mouse. */
351    protected int currentMouseX;
352
353    /** The current Y position of the mouse. */
354    protected int currentMouseY;
355
356    /**
357     * The offset between the current slider value and the cursor's position.
358     */
359    protected int offset;
360
361    /**
362     * Called when the mouse has been dragged. This should find the mouse's
363     * current position and adjust the value of the {@link JSlider}
364     * accordingly.
365     *
366     * @param e A {@link MouseEvent}
367     */
368    public void mouseDragged(MouseEvent e)
369    {
370      dragging = true;
371      if (slider.isEnabled())
372        {
373          currentMouseX = e.getX();
374          currentMouseY = e.getY();
375          if (slider.getValueIsAdjusting())
376            {
377              int value;
378              if (slider.getOrientation() == JSlider.HORIZONTAL)
379                value = valueForXPosition(currentMouseX) - offset;
380              else
381                value = valueForYPosition(currentMouseY) - offset;
382
383              slider.setValue(value);
384            }
385        }
386    }
387
388    /**
389     * Called when the mouse has moved over a component but no buttons have
390     * been pressed yet.
391     *
392     * @param e A {@link MouseEvent}
393     */
394    public void mouseMoved(MouseEvent e)
395    {
396      // Don't care that we're moved unless we're dragging.
397    }
398
399    /**
400     * Called when the mouse is pressed. When the press occurs on the thumb
401     * itself, the {@link JSlider} should have its value set to where the
402     * mouse was pressed. If the press occurs on the track, then the thumb
403     * should move one block towards the direction of the mouse.
404     *
405     * @param e A {@link MouseEvent}
406     */
407    public void mousePressed(MouseEvent e)
408    {
409      if (slider.isEnabled())
410        {
411          currentMouseX = e.getX();
412          currentMouseY = e.getY();
413
414          int value;
415          if (slider.getOrientation() == JSlider.HORIZONTAL)
416            value = valueForXPosition(currentMouseX);
417          else
418            value = valueForYPosition(currentMouseY);
419
420          if (slider.getSnapToTicks())
421            value = findClosestTick(value);
422
423          // If the thumb is hit, then we don't need to set the timers to 
424          // move it. 
425          if (! thumbRect.contains(e.getPoint()))
426            {
427              // The mouse has hit some other part of the slider.
428              // The value moves no matter where in the slider you hit.
429              if (value > slider.getValue())
430                scrollDueToClickInTrack(POSITIVE_SCROLL);
431              else
432                scrollDueToClickInTrack(NEGATIVE_SCROLL);
433            }
434          else
435            {
436              slider.setValueIsAdjusting(true);
437              offset = value - slider.getValue();
438            }
439        }
440    }
441
442    /**
443     * Called when the mouse is released.  This should stop the timer that
444     * scrolls the thumb.
445     *
446     * @param e A {@link MouseEvent}
447     */
448    public void mouseReleased(MouseEvent e)
449    {
450      dragging = false;
451      if (slider.isEnabled())
452        {
453          currentMouseX = e.getX();
454          currentMouseY = e.getY();
455
456          if (slider.getValueIsAdjusting())
457            {
458              slider.setValueIsAdjusting(false);
459              if (slider.getSnapToTicks())
460                slider.setValue(findClosestTick(slider.getValue()));
461            }
462          if (scrollTimer != null)
463            scrollTimer.stop();
464        }
465      slider.repaint();
466    }
467
468    /**
469     * Indicates whether the thumb should scroll in the given direction.
470     *
471     * @param direction The direction to check.
472     *
473     * @return True if the thumb should move in that direction.
474     */
475    public boolean shouldScroll(int direction)
476    {
477      int value;
478      if (slider.getOrientation() == JSlider.HORIZONTAL)
479        value = valueForXPosition(currentMouseX);
480      else
481        value = valueForYPosition(currentMouseY);
482
483      if (direction == POSITIVE_SCROLL)
484        return value > slider.getValue();
485      else
486        return value < slider.getValue();
487    }
488  }
489
490  /**
491   * This class is no longer used as of JDK1.3.
492   */
493  public class ActionScroller extends AbstractAction
494  {
495    /**
496     * Not used.
497     *
498     * @param slider not used
499     * @param dir not used
500     * @param block not used
501     */
502    public ActionScroller(JSlider slider, int dir, boolean block)
503    {
504      // Not used.
505    }
506
507    /**
508     * Not used.
509     *
510     * @param event not used
511     */
512    public void actionPerformed(ActionEvent event)
513    {
514      // Not used.
515    }
516  }
517
518  /** Listener for changes from the model. */
519  protected ChangeListener changeListener;
520
521  /** Listener for changes to the {@link JSlider}. */
522  protected PropertyChangeListener propertyChangeListener;
523
524  /** Listener for the scrollTimer. */
525  protected ScrollListener scrollListener;
526
527  /** Listener for component resizing. */
528  protected ComponentListener componentListener;
529
530  /** Listener for focus handling. */
531  protected FocusListener focusListener;
532
533  /** Listener for mouse events. */
534  protected TrackListener trackListener;
535
536  /** The insets between the FocusRectangle and the ContentRectangle. */
537  protected Insets focusInsets;
538
539  /** The {@link JSlider}'s insets. */
540  protected Insets insetCache;
541
542  /** Rectangle describing content bounds. See diagram above. */
543  protected Rectangle contentRect;
544
545  /** Rectangle describing focus bounds. See diagram above. */
546  protected Rectangle focusRect;
547
548  /** Rectangle describing the thumb's bounds. See diagram above. */
549  protected Rectangle thumbRect;
550
551  /** Rectangle describing the tick bounds. See diagram above. */
552  protected Rectangle tickRect;
553
554  /** Rectangle describing the label bounds. See diagram above. */
555  protected Rectangle labelRect;
556
557  /** Rectangle describing the track bounds. See diagram above. */
558  protected Rectangle trackRect;
559
560  /** FIXME: use this somewhere. */
561  public static final int MAX_SCROLL = 2;
562
563  /** FIXME: use this somewhere. */
564  public static final int MIN_SCROLL = -2;
565
566  /** A constant describing scrolling towards the minimum. */
567  public static final int NEGATIVE_SCROLL = -1;
568
569  /** A constant describing scrolling towards the maximum. */
570  public static final int POSITIVE_SCROLL = 1;
571
572  /** The gap between the edges of the contentRect and trackRect. */
573  protected int trackBuffer;
574
575  /** Whether this slider is actually drawn left to right. */
576  protected boolean leftToRightCache;
577
578  /** A timer that periodically moves the thumb. */
579  protected Timer scrollTimer;
580
581  /** A reference to the {@link JSlider} that this UI was created for. */
582  protected JSlider slider;
583
584  /** The shadow color. */
585  private transient Color shadowColor;
586
587  /** The highlight color. */
588  private transient Color highlightColor;
589
590  /** The focus color. */
591  private transient Color focusColor;
592
593  /** True if the user is dragging the slider. */
594  boolean dragging;
595
596  /**
597   * Creates a new Basic look and feel Slider UI.
598   *
599   * @param b The {@link JSlider} that this UI was created for.
600   */
601  public BasicSliderUI(JSlider b)
602  {
603    super();
604  }
605
606  /**
607   * Returns true if the user is dragging the slider.
608   * 
609   * @return true if the slider is being dragged.
610   * 
611   * @since 1.5
612   */
613  protected boolean isDragging()
614  {
615    return dragging;
616  }
617  
618  /**
619   * Gets the shadow color to be used for this slider. The shadow color is the
620   * color used for drawing the top and left edges of the track.
621   *
622   * @return The shadow color.
623   */
624  protected Color getShadowColor()
625  {
626    return shadowColor;
627  }
628
629  /**
630   * Gets the highlight color to be used for this slider. The highlight color
631   * is the color used for drawing the bottom and right edges of the track.
632   *
633   * @return The highlight color.
634   */
635  protected Color getHighlightColor()
636  {
637    return highlightColor;
638  }
639
640  /**
641   * Gets the focus color to be used for this slider. The focus color is the
642   * color used for drawing the focus rectangle when the component gains
643   * focus.
644   *
645   * @return The focus color.
646   */
647  protected Color getFocusColor()
648  {
649    return focusColor;
650  }
651
652  /**
653   * Factory method to create a BasicSliderUI for the given {@link
654   * JComponent}, which should be a {@link JSlider}.
655   *
656   * @param b The {@link JComponent} a UI is being created for.
657   *
658   * @return A BasicSliderUI for the {@link JComponent}.
659   */
660  public static ComponentUI createUI(JComponent b)
661  {
662    return new BasicSliderUI((JSlider) b);
663  }
664
665  /**
666   * Installs and initializes all fields for this UI delegate. Any properties
667   * of the UI that need to be initialized and/or set to defaults will be
668   * done now. It will also install any listeners necessary.
669   *
670   * @param c The {@link JComponent} that is having this UI installed.
671   */
672  public void installUI(JComponent c)
673  {
674    super.installUI(c);
675    if (c instanceof JSlider)
676      {
677        slider = (JSlider) c;
678
679        focusRect = new Rectangle();
680        contentRect = new Rectangle();
681        thumbRect = new Rectangle();
682        trackRect = new Rectangle();
683        tickRect = new Rectangle();
684        labelRect = new Rectangle();
685
686        insetCache = slider.getInsets();
687        leftToRightCache = ! slider.getInverted();
688
689        scrollTimer = new Timer(200, null);
690        scrollTimer.setRepeats(true);
691
692        installDefaults(slider);
693        installListeners(slider);
694        installKeyboardActions(slider);
695
696        calculateFocusRect();
697
698        calculateContentRect();
699        calculateThumbSize();
700        calculateTrackBuffer();
701        calculateTrackRect();
702        calculateThumbLocation();
703
704        calculateTickRect();
705        calculateLabelRect();
706      }
707  }
708
709  /**
710   * Performs the opposite of installUI. Any properties or resources that need
711   * to be cleaned up will be done now. It will also uninstall any listeners
712   * it has. In addition, any properties of this UI will be nulled.
713   *
714   * @param c The {@link JComponent} that is having this UI uninstalled.
715   */
716  public void uninstallUI(JComponent c)
717  {
718    super.uninstallUI(c);
719
720    uninstallKeyboardActions(slider);
721    uninstallListeners(slider);
722
723    scrollTimer = null;
724
725    focusRect = null;
726    contentRect = null;
727    thumbRect = null;
728    trackRect = null;
729    tickRect = null;
730    labelRect = null;
731
732    focusInsets = null;
733  }
734
735  /**
736   * Initializes any default properties that this UI has from the defaults for
737   * the Basic look and feel.
738   *
739   * @param slider The {@link JSlider} that is having this UI installed.
740   */
741  protected void installDefaults(JSlider slider)
742  {
743    LookAndFeel.installColors(slider, "Slider.background",
744                              "Slider.foreground");
745    LookAndFeel.installBorder(slider, "Slider.border");
746    shadowColor = UIManager.getColor("Slider.shadow");
747    highlightColor = UIManager.getColor("Slider.highlight");
748    focusColor = UIManager.getColor("Slider.focus");
749    focusInsets = UIManager.getInsets("Slider.focusInsets");
750    slider.setOpaque(true);
751  }
752
753  /**
754   * Creates a new {@link TrackListener}.
755   *
756   * @param slider The {@link JSlider} that this {@link TrackListener} is
757   *        created for.
758   *
759   * @return A new {@link TrackListener}.
760   */
761  protected TrackListener createTrackListener(JSlider slider)
762  {
763    return new TrackListener();
764  }
765
766  /**
767   * Creates a new {@link ChangeListener}.
768   *
769   * @param slider The {@link JSlider} that this {@link ChangeListener} is
770   *        created for.
771   *
772   * @return A new {@link ChangeListener}.
773   */
774  protected ChangeListener createChangeListener(JSlider slider)
775  {
776    return new ChangeHandler();
777  }
778
779  /**
780   * Creates a new {@link ComponentListener}.
781   *
782   * @param slider The {@link JSlider} that this {@link ComponentListener} is
783   *        created for.
784   *
785   * @return A new {@link ComponentListener}.
786   */
787  protected ComponentListener createComponentListener(JSlider slider)
788  {
789    return new ComponentHandler();
790  }
791
792  /**
793   * Creates a new {@link FocusListener}.
794   *
795   * @param slider The {@link JSlider} that this {@link FocusListener} is
796   *        created for.
797   *
798   * @return A new {@link FocusListener}.
799   */
800  protected FocusListener createFocusListener(JSlider slider)
801  {
802    return new FocusHandler();
803  }
804
805  /**
806   * Creates a new {@link ScrollListener}.
807   *
808   * @param slider The {@link JSlider} that this {@link ScrollListener} is
809   *        created for.
810   *
811   * @return A new {@link ScrollListener}.
812   */
813  protected ScrollListener createScrollListener(JSlider slider)
814  {
815    return new ScrollListener();
816  }
817
818  /**
819   * Creates a new {@link PropertyChangeListener}.
820   *
821   * @param slider The {@link JSlider} that this {@link
822   *        PropertyChangeListener} is created for.
823   *
824   * @return A new {@link PropertyChangeListener}.
825   */
826  protected PropertyChangeListener createPropertyChangeListener(JSlider slider)
827  {
828    return new PropertyChangeHandler();
829  }
830
831  /**
832   * Creates and registers all the listeners for this UI delegate. This
833   * includes creating the ScrollListener and registering it to the timer.
834   *
835   * @param slider The {@link JSlider} is having listeners installed.
836   */
837  protected void installListeners(JSlider slider)
838  {
839    propertyChangeListener = createPropertyChangeListener(slider);
840    componentListener = createComponentListener(slider);
841    trackListener = createTrackListener(slider);
842    focusListener = createFocusListener(slider);
843    changeListener = createChangeListener(slider);
844    scrollListener = createScrollListener(slider);
845
846    slider.addPropertyChangeListener(propertyChangeListener);
847    slider.addComponentListener(componentListener);
848    slider.addMouseListener(trackListener);
849    slider.addMouseMotionListener(trackListener);
850    slider.addFocusListener(focusListener);
851    slider.getModel().addChangeListener(changeListener);
852
853    scrollTimer.addActionListener(scrollListener);
854  }
855
856  /**
857   * Unregisters all the listeners that this UI delegate was using. In
858   * addition, it will also null any listeners that it was using.
859   *
860   * @param slider The {@link JSlider} that is having listeners removed.
861   */
862  protected void uninstallListeners(JSlider slider)
863  {
864    slider.removePropertyChangeListener(propertyChangeListener);
865    slider.removeComponentListener(componentListener);
866    slider.removeMouseListener(trackListener);
867    slider.removeMouseMotionListener(trackListener);
868    slider.removeFocusListener(focusListener);
869    slider.getModel().removeChangeListener(changeListener);
870
871    scrollTimer.removeActionListener(scrollListener);
872
873    propertyChangeListener = null;
874    componentListener = null;
875    trackListener = null;
876    focusListener = null;
877    changeListener = null;
878    scrollListener = null;
879  }
880
881  /**
882   * Installs any keyboard actions. The list of keys that need to be bound are
883   * listed in Basic look and feel's defaults.
884   *
885   * @param slider The {@link JSlider} that is having keyboard actions
886   *        installed.
887   */
888  protected void installKeyboardActions(JSlider slider)
889  {
890    InputMap keyMap = getInputMap(JComponent.WHEN_FOCUSED);
891    SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, keyMap);
892    ActionMap map = getActionMap();
893    SwingUtilities.replaceUIActionMap(slider, map);
894  }
895
896  /**
897   * Uninstalls any keyboard actions. The list of keys used  are listed in
898   * Basic look and feel's defaults.
899   *
900   * @param slider The {@link JSlider} that is having keyboard actions
901   *        uninstalled.
902   */
903  protected void uninstallKeyboardActions(JSlider slider)
904  {
905    SwingUtilities.replaceUIActionMap(slider, null);
906    SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, null);
907  }
908
909  /* XXX: This is all after experimentation with SUN's implementation.
910
911     PreferredHorizontalSize seems to be 200x21.
912     PreferredVerticalSize seems to be 21x200.
913
914     MinimumHorizontalSize seems to be 36x21.
915     MinimumVerticalSize seems to be 21x36.
916
917     PreferredSize seems to be 200x63. Or Components.getBounds?
918
919     MinimumSize seems to be 36x63.
920
921     MaximumSize seems to be 32767x63.
922   */
923
924  /**
925   * This method returns the preferred size when the slider is horizontally
926   * oriented.
927   *
928   * @return The dimensions of the preferred horizontal size.
929   */
930  public Dimension getPreferredHorizontalSize()
931  {
932    Dimension dim = UIManager.getDimension("Slider.horizontalSize");
933    if (dim == null) // Just to be sure we mirror the default.
934      dim = new Dimension(200, 21);
935    return dim;
936  }
937
938  /**
939   * This method returns the preferred size when the slider is vertically
940   * oriented.
941   *
942   * @return The dimensions of the preferred vertical size.
943   */
944  public Dimension getPreferredVerticalSize()
945  {
946    Dimension dim = UIManager.getDimension("Slider.verticalSize");
947    if (dim == null) // Just to be sure we mirror the default.
948      dim = new Dimension(21, 200);
949    return dim;
950  }
951
952  /**
953   * This method returns the minimum size when the slider is horizontally
954   * oriented.
955   *
956   * @return The dimensions of the minimum horizontal size.
957   */
958  public Dimension getMinimumHorizontalSize()
959  {
960    Dimension dim = UIManager.getDimension("Slider.minimumHorizontalSize");
961    if (dim == null) // Just to be sure we mirror the default.
962      dim = new Dimension(36, 21);
963    return dim;
964  }
965
966  /**
967   * This method returns the minimum size of the slider when it  is vertically
968   * oriented.
969   *
970   * @return The dimensions of the minimum vertical size.
971   */
972  public Dimension getMinimumVerticalSize()
973  {
974    Dimension dim = UIManager.getDimension("Slider.minimumVerticalSize");
975    if (dim == null) // Just to be sure we mirror the default.
976      dim = new Dimension(21, 36);
977    return dim;
978  }
979
980  /**
981   * This method returns the preferred size of the component. If it returns
982   * null, then it is up to the Layout Manager to give the {@link JComponent}
983   * a size.
984   *
985   * @param c The {@link JComponent} to find the preferred size for.
986   *
987   * @return The dimensions of the preferred size.
988   */
989  public Dimension getPreferredSize(JComponent c)
990  {
991    recalculateIfInsetsChanged();
992    Dimension dim;
993    if (slider.getOrientation() == JSlider.HORIZONTAL)
994      {
995        // Create copy here to protect the UIManager value.
996        dim = new Dimension(getPreferredHorizontalSize());
997        dim.height = insetCache.top + insetCache.bottom;
998        dim.height += focusInsets.top + focusInsets.bottom;
999        dim.height += trackRect.height + tickRect.height + labelRect.height;
1000      }
1001    else
1002      {
1003        // Create copy here to protect the UIManager value.
1004        dim = new Dimension(getPreferredVerticalSize());
1005        dim.width = insetCache.left + insetCache.right;
1006        dim.width += focusInsets.left + focusInsets.right;
1007        dim.width += trackRect.width + tickRect.width + labelRect.width;
1008      }
1009    return dim;
1010  }
1011
1012  /**
1013   * This method returns the minimum size for this {@link JSlider}  for this
1014   * look and feel. If it returns null, then it is up to the Layout Manager
1015   * to give the {@link JComponent} a size.
1016   *
1017   * @param c The {@link JComponent} to find the minimum size for.
1018   *
1019   * @return The dimensions of the minimum size.
1020   */
1021  public Dimension getMinimumSize(JComponent c)
1022  {
1023    recalculateIfInsetsChanged();
1024    Dimension dim;
1025    if (slider.getOrientation() == JSlider.HORIZONTAL)
1026      {
1027        // Create copy here to protect the UIManager value.
1028        dim = new Dimension(getMinimumHorizontalSize());
1029        dim.height = insetCache.top + insetCache.bottom;
1030        dim.height += focusInsets.top + focusInsets.bottom;
1031        dim.height += trackRect.height + tickRect.height + labelRect.height;
1032      }
1033    else
1034      {
1035        // Create copy here to protect the UIManager value.
1036        dim = new Dimension(getMinimumVerticalSize());
1037        dim.width = insetCache.left + insetCache.right;
1038        dim.width += focusInsets.left + focusInsets.right;
1039        dim.width += trackRect.width + tickRect.width + labelRect.width;
1040      }
1041    return dim;
1042  }
1043
1044  /**
1045   * This method returns the maximum size for this {@link JSlider} for this
1046   * look and feel.
1047   *
1048   * @param c The {@link JComponent} to find a maximum size for.
1049   *
1050   * @return The dimensions of the maximum size.
1051   */
1052  public Dimension getMaximumSize(JComponent c)
1053  {
1054    Dimension dim = getPreferredSize(c);
1055    if (slider.getOrientation() == JSlider.HORIZONTAL)
1056      dim.width = Short.MAX_VALUE;
1057    else
1058      dim.height = Short.MAX_VALUE;
1059    return dim;
1060  }
1061
1062  /**
1063   * This method calculates all the sizes of the rectangles by delegating to
1064   * the helper methods calculateXXXRect.
1065   */
1066  protected void calculateGeometry()
1067  {
1068    calculateFocusRect();
1069    calculateContentRect();
1070    calculateThumbSize();
1071    calculateTrackBuffer();
1072    calculateTrackRect();
1073    calculateTickRect();
1074    calculateLabelRect();
1075    calculateThumbLocation();
1076  }
1077
1078  /**
1079   * This method calculates the size and position of the focusRect. This
1080   * method does not need to be called if the orientation changes.
1081   */
1082  protected void calculateFocusRect()
1083  {
1084    focusRect.x = insetCache.left;
1085    focusRect.y = insetCache.top;
1086    focusRect.width = slider.getWidth() - insetCache.left - insetCache.right;
1087    focusRect.height = slider.getHeight() - insetCache.top - insetCache.bottom;
1088  }
1089
1090  /**
1091   * Sets the width and height of the <code>thumbRect</code> field, using the
1092   * dimensions returned by {@link #getThumbSize()}.
1093   */
1094  protected void calculateThumbSize()
1095  {
1096    Dimension d = getThumbSize();
1097    thumbRect.width = d.width;
1098    thumbRect.height = d.height;
1099  }
1100
1101  /**
1102   * Updates the <code>contentRect</code> field to an area inside the 
1103   * <code>focusRect</code>. This method does not need to be called if the 
1104   * orientation changes.
1105   */
1106  protected void calculateContentRect()
1107  {
1108    contentRect.x = focusRect.x + focusInsets.left;
1109    contentRect.y = focusRect.y + focusInsets.top;
1110    
1111    contentRect.width = focusRect.width - focusInsets.left - focusInsets.right;
1112    contentRect.height = focusRect.height - focusInsets.top
1113                         - focusInsets.bottom;
1114  }
1115
1116  /**
1117   * Calculates the position of the thumbRect based on the current value of
1118   * the slider. It must take into  account the orientation of the slider.
1119   */
1120  protected void calculateThumbLocation()
1121  {
1122    int value = slider.getValue();
1123
1124    if (slider.getOrientation() == JSlider.HORIZONTAL)
1125      {
1126        thumbRect.x = xPositionForValue(value) - thumbRect.width / 2;
1127        thumbRect.y = trackRect.y + 1;
1128      }
1129    else
1130      {
1131        thumbRect.x = trackRect.x + 1;
1132        thumbRect.y = yPositionForValue(value) - thumbRect.height / 2;
1133      }
1134  }
1135
1136  /**
1137   * Calculates the gap size between the edge of the <code>contentRect</code> 
1138   * and the edge of the <code>trackRect</code>, storing the result in the
1139   * <code>trackBuffer</code> field.  Sufficient space needs to be reserved 
1140   * for the slider thumb and/or the labels at each end of the slider track.
1141   */
1142  protected void calculateTrackBuffer()
1143  {
1144    if (slider.getOrientation() == JSlider.HORIZONTAL)
1145      {
1146        int w = Math.max(getWidthOfLowValueLabel(), getWidthOfHighValueLabel());
1147        trackBuffer = Math.max(thumbRect.width / 2, w / 2);
1148        
1149      }
1150    else
1151      {
1152        int h = Math.max(getHeightOfLowValueLabel(), 
1153                         getHeightOfHighValueLabel());
1154        trackBuffer = Math.max(thumbRect.height / 2, h / 2);
1155      }
1156  }
1157
1158  /**
1159   * Returns the size of the slider's thumb.  The size is hard coded to
1160   * <code>11 x 20</code> for horizontal sliders, and <code>20 x 11</code> for 
1161   * vertical sliders. Note that a new instance of {@link Dimension} is 
1162   * returned for every call to this method (this seems wasteful, but 
1163   * {@link Dimension} instances are not immutable, so this is probably 
1164   * unavoidable).
1165   *
1166   * @return The size of the slider's thumb.
1167   */
1168  protected Dimension getThumbSize()
1169  {
1170    if (slider.getOrientation() == JSlider.HORIZONTAL)
1171      return new Dimension(11, 20);
1172    else
1173      return new Dimension(20, 11);
1174  }
1175
1176  /**
1177   * Calculates the size and position of the trackRect. It must take into
1178   * account the orientation of the slider.
1179   */
1180  protected void calculateTrackRect()
1181  {
1182    if (slider.getOrientation() == JSlider.HORIZONTAL)
1183      {
1184        int center = thumbRect.height;
1185        if (slider.getPaintTicks())
1186          center += getTickLength();
1187        if (slider.getPaintLabels())
1188          center += getHeightOfTallestLabel();
1189        trackRect.x = contentRect.x + trackBuffer;
1190        trackRect.y = contentRect.y + (contentRect.height - center - 1) / 2;
1191        trackRect.width = contentRect.width - 2 * trackBuffer;
1192        trackRect.height = thumbRect.height;
1193      }
1194    else
1195      {
1196        int center = thumbRect.width;
1197        if (slider.getPaintTicks())
1198          center += getTickLength();
1199        if (slider.getPaintLabels())
1200          center += getWidthOfWidestLabel();
1201        trackRect.x = contentRect.x + (contentRect.width - center - 1) / 2;
1202        trackRect.y = contentRect.y + trackBuffer;
1203        trackRect.width = thumbRect.width;
1204        trackRect.height = contentRect.height - 2 * trackBuffer;
1205      }
1206  }
1207
1208  /**
1209   * This method returns the height of the tick area box if the slider  is
1210   * horizontal and the width of the tick area box is the slider is vertical.
1211   * It not necessarily how long the ticks will be. If a gap between the edge
1212   * of tick box and the actual tick is desired, then that will need to be
1213   * handled in the tick painting methods.
1214   *
1215   * @return The height (or width if the slider is vertical) of the tick
1216   *         rectangle.
1217   */
1218  protected int getTickLength()
1219  {
1220    return 8;
1221  }
1222
1223  /**
1224   * This method calculates the size and position of the tickRect. It must
1225   * take into account the orientation of the slider.
1226   */
1227  protected void calculateTickRect()
1228  {
1229    if (slider.getOrientation() == JSlider.HORIZONTAL)
1230      {
1231        tickRect.x = trackRect.x;
1232        tickRect.y = trackRect.y + trackRect.height;
1233        tickRect.width = trackRect.width;
1234        tickRect.height = getTickLength();
1235        
1236        // this makes our Mauve tests pass...can't explain it!
1237        if (!slider.getPaintTicks())
1238          {
1239            tickRect.y--;
1240            tickRect.height = 0;
1241          }
1242      }
1243    else
1244      {
1245        tickRect.x = trackRect.x + trackRect.width;
1246        tickRect.y = trackRect.y;
1247        tickRect.width = getTickLength();
1248        tickRect.height = trackRect.height;
1249
1250        // this makes our Mauve tests pass...can't explain it!
1251        if (!slider.getPaintTicks())
1252          {
1253            tickRect.x--;
1254            tickRect.width = 0;
1255          }
1256      }
1257  }
1258
1259  /**
1260   * Calculates the <code>labelRect</code> field, taking into account the 
1261   * orientation of the slider.
1262   */
1263  protected void calculateLabelRect()
1264  {
1265    if (slider.getOrientation() == JSlider.HORIZONTAL)
1266      {
1267        if (slider.getPaintLabels())
1268          {
1269            labelRect.x = tickRect.x - trackBuffer;
1270            labelRect.y = tickRect.y + tickRect.height;
1271            labelRect.width = tickRect.width + trackBuffer * 2;
1272            labelRect.height = getHeightOfTallestLabel();
1273          }
1274        else
1275          {
1276            labelRect.x = tickRect.x;
1277            labelRect.y = tickRect.y + tickRect.height;
1278            labelRect.width = tickRect.width;
1279            labelRect.height = 0;
1280          }
1281      }
1282    else
1283      {
1284        if (slider.getPaintLabels())
1285          {
1286            labelRect.x = tickRect.x + tickRect.width;
1287            labelRect.y = tickRect.y - trackBuffer;
1288            labelRect.width = getWidthOfWidestLabel();
1289            labelRect.height = tickRect.height + trackBuffer * 2;
1290          }
1291        else
1292          {
1293            labelRect.x = tickRect.x + tickRect.width;
1294            labelRect.y = tickRect.y;
1295            labelRect.width = 0;
1296            labelRect.height = tickRect.height;
1297          }
1298      }
1299  }
1300
1301  /**
1302   * This method returns the width of the widest label  in the slider's label
1303   * table.
1304   *
1305   * @return The width of the widest label or 0 if no label table exists.
1306   */
1307  protected int getWidthOfWidestLabel()
1308  {
1309    int widest = 0;
1310    Dictionary table = slider.getLabelTable();
1311    if (table != null)
1312      {
1313        for (Enumeration list = slider.getLabelTable().elements();
1314             list.hasMoreElements();)
1315          {
1316            Component label = (Component) list.nextElement();
1317            widest = Math.max(label.getPreferredSize().width, widest);
1318          }
1319      }
1320    return widest;
1321  }
1322
1323  /**
1324   * This method returns the height of the tallest label in the slider's label
1325   * table.
1326   *
1327   * @return The height of the tallest label or 0 if no label table exists.
1328   */
1329  protected int getHeightOfTallestLabel()
1330  {
1331    int tallest = 0;
1332    Component label;
1333
1334    if (slider.getLabelTable() == null)
1335      return 0;
1336    Dimension pref;
1337    for (Enumeration list = slider.getLabelTable().elements();
1338         list.hasMoreElements();)
1339      {
1340        Object comp = list.nextElement();
1341        if (! (comp instanceof Component))
1342          continue;
1343        label = (Component) comp;
1344        pref = label.getPreferredSize();
1345        if (pref != null && pref.height > tallest)
1346          tallest = pref.height;
1347      }
1348    return tallest;
1349  }
1350
1351  /**
1352   * Returns the width of the label whose key has the highest value, or 0 if
1353   * there are no labels.
1354   *
1355   * @return The width of the label whose key has the highest value.
1356   * 
1357   * @see #getHighestValueLabel()
1358   */
1359  protected int getWidthOfHighValueLabel()
1360  {
1361    Component highValueLabel = getHighestValueLabel();
1362    if (highValueLabel != null)
1363      return highValueLabel.getPreferredSize().width;
1364    else
1365      return 0;
1366  }
1367
1368  /**
1369   * Returns the width of the label whose key has the lowest value, or 0 if
1370   * there are no labels.
1371   *
1372   * @return The width of the label whose key has the lowest value.
1373   * 
1374   * @see #getLowestValueLabel()
1375   */
1376  protected int getWidthOfLowValueLabel()
1377  {
1378    Component lowValueLabel = getLowestValueLabel();
1379    if (lowValueLabel != null)
1380      return lowValueLabel.getPreferredSize().width;
1381    else
1382      return 0;
1383  }
1384
1385  /**
1386   * Returns the height of the label whose key has the highest value, or 0 if
1387   * there are no labels.
1388   *
1389   * @return The height of the high value label or 0 if no label table exists.
1390   */
1391  protected int getHeightOfHighValueLabel()
1392  {
1393    Component highValueLabel = getHighestValueLabel();
1394    if (highValueLabel != null)
1395      return highValueLabel.getPreferredSize().height;
1396    else
1397      return 0;
1398  }
1399
1400  /**
1401   * Returns the height of the label whose key has the lowest value, or 0 if
1402   * there are no labels.
1403   *
1404   * @return The height of the low value label or 0 if no label table exists.
1405   */
1406  protected int getHeightOfLowValueLabel()
1407  {
1408    Component lowValueLabel = getLowestValueLabel();
1409    if (lowValueLabel != null)
1410      return lowValueLabel.getPreferredSize().height;
1411    else
1412      return 0;
1413  }
1414
1415  /**
1416   * Returns <code>true</code> if the slider scale is to be drawn inverted,
1417   * and <code>false</code> if not.
1418   *
1419   * @return <code>true</code> if the slider is to be drawn inverted.
1420   */
1421  protected boolean drawInverted()
1422  {
1423    return slider.getInverted();
1424  }
1425
1426  /**
1427   * This method returns the label whose key has the lowest value.
1428   *
1429   * @return The low value label or null if no label table exists.
1430   */
1431  protected Component getLowestValueLabel()
1432  {
1433    Integer key = new Integer(Integer.MAX_VALUE);
1434    Integer tmpKey;
1435    Dictionary labelTable = slider.getLabelTable();
1436
1437    if (labelTable == null)
1438      return null;
1439
1440    for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
1441      {
1442        Object value = list.nextElement();
1443        if (! (value instanceof Integer))
1444          continue;
1445        tmpKey = (Integer) value;
1446        if (tmpKey.intValue() < key.intValue())
1447          key = tmpKey;
1448      }
1449    Object comp = labelTable.get(key);
1450    if (! (comp instanceof Component))
1451      return null;
1452    return (Component) comp;
1453  }
1454
1455  /**
1456   * Returns the label whose key has the highest value.
1457   *
1458   * @return The label whose key has the highest value or <code>null</code> if 
1459   *     no label table exists.
1460   */
1461  protected Component getHighestValueLabel()
1462  {
1463    Integer key = new Integer(Integer.MIN_VALUE);
1464    Integer tmpKey;
1465    Dictionary labelTable = slider.getLabelTable();
1466
1467    if (labelTable == null)
1468      return null;
1469
1470    for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
1471      {
1472        Object value = list.nextElement();
1473        if (! (value instanceof Integer))
1474          continue;
1475        tmpKey = (Integer) value;
1476        if (tmpKey.intValue() > key.intValue())
1477          key = tmpKey;
1478      }
1479    Object comp = labelTable.get(key);
1480    if (! (comp instanceof Component))
1481      return null;
1482    return (Component) comp;
1483  }
1484
1485  /**
1486   * This method is used to paint the {@link JSlider}. It delegates all its
1487   * duties to the various paint methods like paintTicks(),  paintTrack(),
1488   * paintThumb(), etc.
1489   *
1490   * @param g The {@link Graphics} object to paint with.
1491   * @param c The {@link JComponent} that is being painted.
1492   */
1493  public void paint(Graphics g, JComponent c)
1494  {
1495    recalculateIfInsetsChanged();
1496    recalculateIfOrientationChanged();
1497    if (slider.getPaintTrack() && hitClip(g, trackRect))
1498      paintTrack(g);
1499    if (slider.getPaintTicks() && hitClip(g, tickRect))
1500      paintTicks(g);
1501    if (slider.getPaintLabels() && hitClip(g, labelRect))
1502      paintLabels(g);
1503    if (slider.hasFocus() && hitClip(g, focusRect))
1504      paintFocus(g);
1505    if (hitClip(g, thumbRect))
1506      paintThumb(g);
1507  }
1508
1509  /**
1510   * This method recalculates any rectangles that need to be recalculated
1511   * after the insets of the component have changed.
1512   */
1513  protected void recalculateIfInsetsChanged()
1514  {
1515    Insets insets = slider.getInsets();
1516    if (! insets.equals(insetCache))
1517      {
1518        insetCache = insets;
1519        calculateGeometry();
1520      }
1521  }
1522
1523  /**
1524   * This method recalculates any rectangles that need to be recalculated
1525   * after the orientation of the slider changes.
1526   */
1527  protected void recalculateIfOrientationChanged()
1528  {
1529    // Examining a test program shows that either Sun calls private
1530    // methods that we don't know about, or these don't do anything.  
1531    calculateThumbSize();
1532    calculateTrackBuffer();
1533    calculateTrackRect();
1534    calculateThumbLocation();
1535
1536    calculateTickRect();
1537    calculateLabelRect();
1538  }
1539
1540  /**
1541   * This method is called during a repaint if the slider has focus. It draws
1542   * an outline of the  focusRect using the color returned by
1543   * getFocusColor().
1544   *
1545   * @param g The {@link Graphics} object to draw with.
1546   */
1547  public void paintFocus(Graphics g)
1548  {
1549    Color saved_color = g.getColor();
1550
1551    g.setColor(getFocusColor());
1552    
1553    g.drawRect(focusRect.x, focusRect.y, focusRect.width, focusRect.height);
1554
1555    g.setColor(saved_color);
1556  }
1557
1558  /**
1559   * <p>
1560   * This method is called during a repaint if the  track is to be drawn. It
1561   * draws a 3D rectangle to  represent the track. The track is not the size
1562   * of the trackRect. The top and left edges of the track should be outlined
1563   * with the shadow color. The bottom and right edges should be outlined
1564   * with the highlight color.
1565   * </p>
1566   * <pre>
1567   *    a---d   
1568   *    |   |   
1569   *    |   |   a------------------------d
1570   *    |   |   |                        |
1571   *    |   |   b------------------------c
1572   *    |   |
1573   *    |   |   
1574   *    b---c
1575   * </pre>
1576   * 
1577   * <p>
1578   * The b-a-d path needs to be drawn with the shadow color and the b-c-d path
1579   * needs to be drawn with the highlight color.
1580   * </p>
1581   *
1582   * @param g The {@link Graphics} object to draw with.
1583   */
1584  public void paintTrack(Graphics g)
1585  {
1586    Color saved_color = g.getColor();
1587    int width;
1588    int height;
1589
1590    Point a = new Point(trackRect.x, trackRect.y + 1);
1591    Point b = new Point(a);
1592    Point c = new Point(a);
1593    Point d = new Point(a);
1594
1595    if (slider.getOrientation() == JSlider.HORIZONTAL)
1596      {
1597        width = trackRect.width;
1598        height = (thumbRect.height / 4 == 0) ? 1 : thumbRect.height / 4;
1599
1600        a.translate(0, (trackRect.height / 2) - (height / 2));
1601        b.translate(0, (trackRect.height / 2) + (height / 2));
1602        c.translate(trackRect.width, (trackRect.height / 2) + (height / 2));
1603        d.translate(trackRect.width, (trackRect.height / 2) - (height / 2));
1604      }
1605    else
1606      {
1607        width = (thumbRect.width / 4 == 0) ? 1 : thumbRect.width / 4;
1608        height = trackRect.height;
1609
1610        a.translate((trackRect.width / 2) - (width / 2), 0);
1611        b.translate((trackRect.width / 2) - (width / 2), trackRect.height);
1612        c.translate((trackRect.width / 2) + (width / 2), trackRect.height);
1613        d.translate((trackRect.width / 2) + (width / 2), 0);
1614      }
1615    g.setColor(Color.GRAY);
1616    g.fillRect(a.x, a.y, width, height);
1617
1618    g.setColor(getHighlightColor());
1619    g.drawLine(b.x, b.y, c.x, c.y);
1620    g.drawLine(c.x, c.y, d.x, d.y);
1621
1622    g.setColor(getShadowColor());
1623    g.drawLine(b.x, b.y, a.x, a.y);
1624    g.drawLine(a.x, a.y, d.x, d.y);
1625
1626    g.setColor(saved_color);
1627  }
1628
1629  /**
1630   * This method is called during a repaint if the ticks are to be drawn. This
1631   * method must still verify that the majorTickSpacing and minorTickSpacing
1632   * are greater than zero before drawing the ticks.
1633   *
1634   * @param g The {@link Graphics} object to draw with.
1635   */
1636  public void paintTicks(Graphics g)
1637  {
1638    int max = slider.getMaximum();
1639    int min = slider.getMinimum();
1640    int majorSpace = slider.getMajorTickSpacing();
1641    int minorSpace = slider.getMinorTickSpacing();
1642
1643    if (majorSpace > 0)
1644      {
1645        if (slider.getOrientation() == JSlider.HORIZONTAL)
1646          {
1647            g.translate(0, tickRect.y);
1648            for (int i = min; i <= max; i += majorSpace)
1649              paintMajorTickForHorizSlider(g, tickRect, xPositionForValue(i));
1650            g.translate(0, -tickRect.y);
1651          }
1652        else // JSlider.VERTICAL
1653          {
1654            g.translate(tickRect.x, 0);
1655            for (int i = min; i <= max; i += majorSpace)
1656              paintMajorTickForVertSlider(g, tickRect, yPositionForValue(i));
1657            g.translate(-tickRect.x, 0);
1658          }
1659      }
1660    if (minorSpace > 0)
1661      {
1662        if (slider.getOrientation() == JSlider.HORIZONTAL)
1663          {
1664            g.translate(0, tickRect.y);
1665            for (int i = min; i <= max; i += minorSpace)
1666              paintMinorTickForHorizSlider(g, tickRect, xPositionForValue(i));
1667            g.translate(0, -tickRect.y);
1668          }
1669        else
1670          {
1671            g.translate(tickRect.x, 0);
1672            for (int i = min; i <= max; i += minorSpace)
1673              paintMinorTickForVertSlider(g, tickRect, yPositionForValue(i));
1674            g.translate(-tickRect.x, 0);
1675          }
1676      }
1677  }
1678
1679  /* Minor ticks start at 1/4 of the height (or width) of the tickRect and 
1680     extend to 1/2 of the tickRect.
1681
1682     Major ticks start at 1/4 of the height and extend to 3/4.
1683   */
1684
1685  /**
1686   * This method paints a minor tick for a horizontal slider at the given x
1687   * value. x represents the x coordinate to paint at.
1688   *
1689   * @param g The {@link Graphics} object to draw with.
1690   * @param tickBounds The tickRect rectangle.
1691   * @param x The x coordinate to draw the tick at.
1692   */
1693  protected void paintMinorTickForHorizSlider(Graphics g,
1694                                              Rectangle tickBounds, int x)
1695  {
1696    int y = tickRect.height / 4;
1697    Color saved = g.getColor();
1698    g.setColor(Color.BLACK);
1699
1700    g.drawLine(x, y, x, y + tickRect.height / 4);
1701    g.setColor(saved);
1702  }
1703
1704  /**
1705   * This method paints a major tick for a horizontal slider at the given x
1706   * value. x represents the x coordinate to paint at.
1707   *
1708   * @param g The {@link Graphics} object to draw with.
1709   * @param tickBounds The tickRect rectangle.
1710   * @param x The x coordinate to draw the tick at.
1711   */
1712  protected void paintMajorTickForHorizSlider(Graphics g,
1713                                              Rectangle tickBounds, int x)
1714  {
1715    int y = tickRect.height / 4;
1716    Color saved = g.getColor();
1717    g.setColor(Color.BLACK);
1718
1719    g.drawLine(x, y, x, y + tickRect.height / 2);
1720    g.setColor(saved);
1721  }
1722
1723  /**
1724   * This method paints a minor tick for a vertical slider at the given y
1725   * value. y represents the y coordinate to paint at.
1726   *
1727   * @param g The {@link Graphics} object to draw with.
1728   * @param tickBounds The tickRect rectangle.
1729   * @param y The y coordinate to draw the tick at.
1730   */
1731  protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds,
1732                                             int y)
1733  {
1734    int x = tickRect.width / 4;
1735    Color saved = g.getColor();
1736    g.setColor(Color.BLACK);
1737
1738    g.drawLine(x, y, x + tickRect.width / 4, y);
1739    g.setColor(saved);
1740  }
1741
1742  /**
1743   * This method paints a major tick for a vertical slider at the given y
1744   * value. y represents the y coordinate to paint at.
1745   *
1746   * @param g The {@link Graphics} object to draw with.
1747   * @param tickBounds The tickRect rectangle.
1748   * @param y The y coordinate to draw the tick at.
1749   */
1750  protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds,
1751                                             int y)
1752  {
1753    int x = tickRect.width / 4;
1754    Color saved = g.getColor();
1755    g.setColor(Color.BLACK);
1756
1757    g.drawLine(x, y, x + tickRect.width / 2, y);
1758    g.setColor(saved);
1759  }
1760
1761  /**
1762   * This method paints all the labels from the slider's label table. This
1763   * method must make sure that the label table is not null before painting
1764   * the labels. Each entry in the label table is a (integer, component)
1765   * pair. Every label is painted at the value of the integer.
1766   *
1767   * @param g The {@link Graphics} object to draw with.
1768   */
1769  public void paintLabels(Graphics g)
1770  {
1771    Dictionary table = slider.getLabelTable();
1772    if (table != null)
1773      {
1774        int min = slider.getMinimum();
1775        int max = slider.getMaximum();
1776        for (Enumeration list = table.keys(); list.hasMoreElements();)
1777          {
1778            Integer key = (Integer) list.nextElement();
1779            int value = key.intValue();
1780            if (value >= min && value <= max)
1781              {
1782                Component label = (Component) table.get(key);
1783                if (slider.getOrientation() == JSlider.HORIZONTAL)
1784                  {
1785                    g.translate(0, labelRect.y);
1786                    paintHorizontalLabel(g, value, label);
1787                    g.translate(0, -labelRect.y);
1788                  }
1789                else
1790                  {
1791                    g.translate(labelRect.x, 0);
1792                    paintVerticalLabel(g, value, label);
1793                    g.translate(-labelRect.x, 0);
1794                  }
1795              }
1796          }
1797      }
1798  }
1799
1800  /**
1801   * This method paints the label on the horizontal slider at the value
1802   * specified. The value is not a coordinate. It is a value within the range
1803   * of the  slider. If the value is not within the range of the slider, this
1804   * method will do nothing. This method should not paint outside the
1805   * boundaries of the labelRect.
1806   *
1807   * @param g The {@link Graphics} object to draw with.
1808   * @param value The value to paint at.
1809   * @param label The label to paint.
1810   */
1811  protected void paintHorizontalLabel(Graphics g, int value, Component label)
1812  {
1813    int center = xPositionForValue(value);
1814    int left = center - label.getPreferredSize().width / 2;
1815    g.translate(left, 0);
1816    label.paint(g);
1817    g.translate(-left, 0);
1818  }
1819
1820  /**
1821   * This method paints the label on the vertical slider at the value
1822   * specified. The value is not a coordinate. It is a value within the range
1823   * of the  slider. If the value is not within the range of the slider, this
1824   * method will do nothing. This method should not paint outside the
1825   * boundaries of the labelRect.
1826   *
1827   * @param g The {@link Graphics} object to draw with.
1828   * @param value The value to paint at.
1829   * @param label The label to paint.
1830   */
1831  protected void paintVerticalLabel(Graphics g, int value, Component label)
1832  {
1833    int center = yPositionForValue(value);
1834    int top = center - label.getPreferredSize().height / 2;
1835    g.translate(0, top);
1836    label.paint(g);
1837    g.translate(0, -top);
1838  }
1839
1840  /**
1841   * <p>
1842   * This method paints a thumb. There are two types of thumb:
1843   * </p>
1844   * <pre>
1845   *   Vertical         Horizontal
1846   *    a---b            a-----b
1847   *    |   |            |      \
1848   *    e   c            |       c
1849   *     \ /             |      /
1850   *      d              e-----d
1851   *  </pre>
1852   * 
1853   * <p>
1854   * In the case of vertical thumbs, we highlight the path b-a-e-d and shadow
1855   * the path b-c-d. In the case of horizontal thumbs, we highlight the path
1856   * c-b-a-e and shadow the path c-d-e. In both cases we fill the path
1857   * a-b-c-d-e before shadows and highlights are drawn.
1858   * </p>
1859   *
1860   * @param g The graphics object to paint with
1861   */
1862  public void paintThumb(Graphics g)
1863  {
1864    Color saved_color = g.getColor();
1865
1866    Point a = new Point(thumbRect.x, thumbRect.y);
1867    Point b = new Point(a);
1868    Point c = new Point(a);
1869    Point d = new Point(a);
1870    Point e = new Point(a);
1871
1872    Polygon bright;
1873    Polygon light; // light shadow
1874    Polygon dark; // dark shadow
1875    Polygon all;
1876
1877    // This will be in X-dimension if the slider is inverted and y if it isn't.
1878    int turnPoint;
1879
1880    if (slider.getOrientation() == JSlider.HORIZONTAL)
1881      {
1882        turnPoint = thumbRect.height * 3 / 4;
1883
1884        b.translate(thumbRect.width - 1, 0);
1885        c.translate(thumbRect.width - 1, turnPoint);
1886        d.translate(thumbRect.width / 2 - 1, thumbRect.height - 1);
1887        e.translate(0, turnPoint);
1888
1889        bright = new Polygon(new int[] { b.x - 1, a.x, e.x, d.x },
1890                             new int[] { b.y, a.y, e.y, d.y }, 4);
1891
1892        dark = new Polygon(new int[] { b.x, c.x, d.x + 1 }, new int[] { b.y,
1893                                                                       c.y - 1,
1894                                                                       d.y }, 3);
1895
1896        light = new Polygon(new int[] { b.x - 1, c.x - 1, d.x + 1 },
1897                            new int[] { b.y + 1, c.y - 1, d.y - 1 }, 3);
1898
1899        all = new Polygon(
1900                          new int[] { a.x + 1, b.x - 2, c.x - 2, d.x, e.x + 1 },
1901                          new int[] { a.y + 1, b.y + 1, c.y - 1, d.y - 1, e.y },
1902                          5);
1903      }
1904    else
1905      {
1906        turnPoint = thumbRect.width * 3 / 4 - 1;
1907
1908        b.translate(turnPoint, 0);
1909        c.translate(thumbRect.width - 1, thumbRect.height / 2);
1910        d.translate(turnPoint, thumbRect.height - 1);
1911        e.translate(0, thumbRect.height - 1);
1912
1913        bright = new Polygon(new int[] { c.x - 1, b.x, a.x, e.x },
1914                             new int[] { c.y - 1, b.y, a.y, e.y - 1 }, 4);
1915
1916        dark = new Polygon(new int[] { c.x, d.x, e.x }, new int[] { c.y, d.y,
1917                                                                   e.y }, 3);
1918
1919        light = new Polygon(new int[] { c.x - 1, d.x, e.x + 1 },
1920                            new int[] { c.y, d.y - 1, e.y - 1 }, 3);
1921        all = new Polygon(new int[] { a.x + 1, b.x, c.x - 2, c.x - 2, d.x,
1922                                     e.x + 1 }, new int[] { a.y + 1, b.y + 1,
1923                                                           c.y - 1, c.y,
1924                                                           d.y - 2, e.y - 2 },
1925                          6);
1926      }
1927
1928    g.setColor(Color.WHITE);
1929    g.drawPolyline(bright.xpoints, bright.ypoints, bright.npoints);
1930
1931    g.setColor(Color.BLACK);
1932    g.drawPolyline(dark.xpoints, dark.ypoints, dark.npoints);
1933
1934    g.setColor(Color.GRAY);
1935    g.drawPolyline(light.xpoints, light.ypoints, light.npoints);
1936
1937    g.setColor(Color.LIGHT_GRAY);
1938    g.drawPolyline(all.xpoints, all.ypoints, all.npoints);
1939    g.fillPolygon(all);
1940
1941    g.setColor(saved_color);
1942  }
1943
1944  /**
1945   * This method sets the position of the thumbRect.
1946   *
1947   * @param x The new x position.
1948   * @param y The new y position.
1949   */
1950  public void setThumbLocation(int x, int y)
1951  {
1952    Rectangle union = new Rectangle(thumbRect);
1953    thumbRect.setLocation(x, y);
1954    SwingUtilities.computeUnion(thumbRect.x, thumbRect.y, thumbRect.width,
1955                                thumbRect.height, union);
1956    slider.repaint(union);
1957  }
1958
1959  /**
1960   * Moves the thumb one block in the direction specified (a block is 1/10th
1961   * of the slider range).   If the slider snaps to ticks, this method is 
1962   * responsible for snapping it to a tick after the thumb has been moved.
1963   *
1964   * @param direction  the direction (positive values increment the thumb 
1965   *   position by one block, zero/negative values decrement the thumb position
1966   *   by one block).
1967   */
1968  public void scrollByBlock(int direction)
1969  {
1970    int unit = (slider.getMaximum() - slider.getMinimum()) / 10;
1971    int moveTo = slider.getValue();
1972    if (direction > 0)
1973      moveTo += unit;
1974    else
1975      moveTo -= unit;
1976
1977    if (slider.getSnapToTicks())
1978      moveTo = findClosestTick(moveTo);
1979
1980    slider.setValue(moveTo);
1981  }
1982
1983  /**
1984   * Moves the thumb one unit in the specified direction. If the slider snaps 
1985   * to ticks, this method is responsible for snapping it to a tick after the 
1986   * thumb has been moved.
1987   *
1988   * @param direction  the direction (positive values increment the thumb 
1989   *   position by one, zero/negative values decrement the thumb position by
1990   *   one).
1991   */
1992  public void scrollByUnit(int direction)
1993  {
1994    int moveTo = slider.getValue();
1995    if (direction > 0)
1996      moveTo++;
1997    else
1998      moveTo--;
1999
2000    if (slider.getSnapToTicks())
2001      moveTo = findClosestTick(moveTo);
2002
2003    slider.setValue(moveTo);
2004  }
2005
2006  /**
2007   * This method is called when there has been a click in the track and the
2008   * thumb needs to be scrolled  on regular intervals. This method is only
2009   * responsible  for starting the timer and not for stopping it.
2010   *
2011   * @param dir The direction to move in.
2012   */
2013  protected void scrollDueToClickInTrack(int dir)
2014  {
2015    scrollTimer.stop();
2016
2017    scrollListener.setDirection(dir);
2018    scrollListener.setScrollByBlock(true);
2019
2020    scrollTimer.start();
2021  }
2022
2023  /**
2024   * Returns the x-coordinate (relative to the component) for the given slider 
2025   * value.  This method assumes that the <code>trackRect</code> field is
2026   * set up.
2027   *
2028   * @param value  the slider value.
2029   *
2030   * @return The x-coordinate.
2031   */
2032  protected int xPositionForValue(int value)
2033  {
2034    int min = slider.getMinimum();
2035    int max = slider.getMaximum();
2036    int len = trackRect.width;
2037    double range = max - min;
2038    double pixPerVal = len / range;
2039    int left = trackRect.x;
2040    int right = left + trackRect.width - 1;
2041    int xpos;
2042    if (! drawInverted())
2043      xpos = left + (int) Math.round(pixPerVal * ((double) value - min));
2044    else
2045      xpos = right - (int) Math.round(pixPerVal * ((double) value - min));
2046    xpos = Math.max(left, xpos);
2047    xpos = Math.min(right, xpos);
2048    return xpos;
2049  }
2050
2051  /**
2052   * Returns the y-coordinate (relative to the component) for the given slider 
2053   * value.  This method assumes that the <code>trackRect</code> field is 
2054   * set up.
2055   *
2056   * @param value  the slider value.
2057   *
2058   * @return The y-coordinate.
2059   */
2060  protected int yPositionForValue(int value)
2061  {
2062    int min = slider.getMinimum();
2063    int max = slider.getMaximum();
2064    int len = trackRect.height;
2065    double range = max - min;
2066    double pixPerVal = len / range;
2067    int top = trackRect.y;
2068    int bottom = top + trackRect.height - 1;
2069    int ypos;
2070    if (! drawInverted())
2071      ypos = top + (int) Math.round(pixPerVal * ((double) max - value));
2072    else
2073      ypos = top + (int) Math.round(pixPerVal * ((double) value - min));
2074    ypos = Math.max(top, ypos);
2075    ypos = Math.min(bottom, ypos);
2076    return ypos;
2077  }
2078
2079  /**
2080   * This method returns the value in the slider's range given the y
2081   * coordinate. If the value is out of range, it will  return the closest
2082   * legal value.
2083   *
2084   * @param yPos The y coordinate to calculate a value for.
2085   *
2086   * @return The value for the y coordinate.
2087   */
2088  public int valueForYPosition(int yPos)
2089  {
2090    int min = slider.getMinimum();
2091    int max = slider.getMaximum();
2092    int len = trackRect.height;
2093
2094    int value;
2095
2096    // If the length is 0, you shouldn't be able to even see where the slider 
2097    // is.  This really shouldn't ever happen, but just in case, we'll return 
2098    // the middle.
2099    if (len == 0)
2100      return (max - min) / 2;
2101
2102    if (! drawInverted())
2103      value = (len - (yPos - trackRect.y)) * (max - min) / len + min;
2104    else
2105      value = (yPos - trackRect.y) * (max - min) / len + min;
2106
2107    // If this isn't a legal value, then we'll have to move to one now.
2108    if (value > max)
2109      value = max;
2110    else if (value < min)
2111      value = min;
2112    return value;
2113  }
2114
2115  /**
2116   * This method returns the value in the slider's range given the x
2117   * coordinate. If the value is out of range, it will return the closest
2118   * legal value.
2119   *
2120   * @param xPos The x coordinate to calculate a value for.
2121   *
2122   * @return The value for the x coordinate.
2123   */
2124  public int valueForXPosition(int xPos)
2125  {
2126    int min = slider.getMinimum();
2127    int max = slider.getMaximum();
2128    int len = trackRect.width;
2129
2130    int value;
2131
2132    // If the length is 0, you shouldn't be able to even see where the slider 
2133    // is.  This really shouldn't ever happen, but just in case, we'll return 
2134    // the middle.
2135    if (len == 0)
2136      return (max - min) / 2;
2137
2138    if (! drawInverted())
2139      value = (xPos - trackRect.x) * (max - min) / len + min;
2140    else
2141      value = (len - (xPos - trackRect.x)) * (max - min) / len + min;
2142
2143    // If this isn't a legal value, then we'll have to move to one now.
2144    if (value > max)
2145      value = max;
2146    else if (value < min)
2147      value = min;
2148    return value;
2149  }
2150
2151  /**
2152   * This method finds the closest value that has a tick associated with it.
2153   * This is package-private to avoid an accessor method.
2154   *
2155   * @param value The value to search from.
2156   *
2157   * @return The closest value that has a tick associated with it.
2158   */
2159  int findClosestTick(int value)
2160  {
2161    int min = slider.getMinimum();
2162    int max = slider.getMaximum();
2163    int majorSpace = slider.getMajorTickSpacing();
2164    int minorSpace = slider.getMinorTickSpacing();
2165
2166    // The default value to return is value + minor or
2167    // value + major. 
2168    // Initializing at min - value leaves us with a default
2169    // return value of min, which always has tick marks
2170    // (if ticks are painted).
2171    int minor = min - value;
2172    int major = min - value;
2173
2174    // If there are no major tick marks or minor tick marks 
2175    // e.g. snap is set to true but no ticks are set, then
2176    // we can just return the value.
2177    if (majorSpace <= 0 && minorSpace <= 0)
2178      return value;
2179
2180    // First check the major ticks.
2181    if (majorSpace > 0)
2182      {
2183        int lowerBound = (value - min) / majorSpace;
2184        int majLower = majorSpace * lowerBound + min;
2185        int majHigher = majorSpace * (lowerBound + 1) + min;
2186
2187        if (majHigher <= max && majHigher - value <= value - majLower)
2188          major = majHigher - value;
2189        else
2190          major = majLower - value;
2191      }
2192
2193    if (minorSpace > 0)
2194      {
2195        int lowerBound = value / minorSpace;
2196        int minLower = minorSpace * lowerBound;
2197        int minHigher = minorSpace * (lowerBound + 1);
2198
2199        if (minHigher <= max && minHigher - value <= value - minLower)
2200          minor = minHigher - value;
2201        else
2202          minor = minLower - value;
2203      }
2204
2205    // Give preference to minor ticks
2206    if (Math.abs(minor) > Math.abs(major))
2207      return value + major;
2208    else
2209      return value + minor;
2210  }
2211  
2212  InputMap getInputMap(int condition) 
2213  {
2214    if (condition == JComponent.WHEN_FOCUSED)
2215      return (InputMap) UIManager.get("Slider.focusInputMap");
2216    return null;
2217  }
2218
2219  /**
2220   * Returns the action map for the {@link JSlider}.  All sliders share
2221   * a single action map which is created the first time this method is 
2222   * called, then stored in the UIDefaults table for subsequent access.
2223   * 
2224   * @return The shared action map.
2225   */
2226  ActionMap getActionMap() 
2227  {
2228    ActionMap map = (ActionMap) UIManager.get("Slider.actionMap");
2229
2230    if (map == null) // first time here
2231      {
2232        map = createActionMap();
2233        if (map != null)
2234          UIManager.put("Slider.actionMap", map);
2235      }
2236    return map;
2237  }
2238
2239  /**
2240   * Creates the action map shared by all {@link JSlider} instances.
2241   * This method is called once by {@link #getActionMap()} when it 
2242   * finds no action map in the UIDefaults table...after the map is 
2243   * created, it gets added to the defaults table so that subsequent 
2244   * calls to {@link #getActionMap()} will return the same shared 
2245   * instance.
2246   * 
2247   * @return The action map.
2248   */
2249  ActionMap createActionMap()
2250  {
2251    ActionMap map = new ActionMapUIResource();
2252    map.put("positiveUnitIncrement", 
2253            new AbstractAction("positiveUnitIncrement") {
2254              public void actionPerformed(ActionEvent event)
2255              {
2256                JSlider slider = (JSlider) event.getSource();
2257                BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2258                if (slider.getInverted())
2259                  ui.scrollByUnit(BasicSliderUI.NEGATIVE_SCROLL);
2260                else
2261                  ui.scrollByUnit(BasicSliderUI.POSITIVE_SCROLL);
2262              }
2263            }
2264    );
2265    map.put("negativeUnitIncrement", 
2266            new AbstractAction("negativeUnitIncrement") {
2267              public void actionPerformed(ActionEvent event)
2268              {
2269                JSlider slider = (JSlider) event.getSource();
2270                BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2271                if (slider.getInverted())
2272                  ui.scrollByUnit(BasicSliderUI.POSITIVE_SCROLL);
2273                else
2274                  ui.scrollByUnit(BasicSliderUI.NEGATIVE_SCROLL);
2275              }
2276            }
2277    );
2278    map.put("positiveBlockIncrement", 
2279            new AbstractAction("positiveBlockIncrement") {
2280              public void actionPerformed(ActionEvent event)
2281              {
2282                JSlider slider = (JSlider) event.getSource();
2283                BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2284                if (slider.getInverted())
2285                  ui.scrollByBlock(BasicSliderUI.NEGATIVE_SCROLL);
2286                else
2287                  ui.scrollByBlock(BasicSliderUI.POSITIVE_SCROLL);
2288              }
2289            }
2290    );
2291    map.put("negativeBlockIncrement", 
2292            new AbstractAction("negativeBlockIncrement") {
2293              public void actionPerformed(ActionEvent event)
2294              {
2295                JSlider slider = (JSlider) event.getSource();
2296                BasicSliderUI ui = (BasicSliderUI) slider.getUI();
2297                if (slider.getInverted())
2298                  ui.scrollByBlock(BasicSliderUI.POSITIVE_SCROLL);
2299                else
2300                  ui.scrollByBlock(BasicSliderUI.NEGATIVE_SCROLL);
2301              }
2302            }
2303    );
2304    map.put("minScroll", 
2305            new AbstractAction("minScroll") {
2306              public void actionPerformed(ActionEvent event)
2307              {
2308                JSlider slider = (JSlider) event.getSource();
2309                if (slider.getInverted())
2310                  slider.setValue(slider.getMaximum());
2311                else
2312                  slider.setValue(slider.getMinimum());   
2313              }
2314            }
2315    );
2316    map.put("maxScroll", 
2317            new AbstractAction("maxScroll") {
2318              public void actionPerformed(ActionEvent event)
2319              {
2320                JSlider slider = (JSlider) event.getSource();
2321                if (slider.getInverted())
2322                  slider.setValue(slider.getMinimum());
2323                else
2324                  slider.setValue(slider.getMaximum());                  
2325              }
2326            }
2327    );
2328    return map;
2329  }
2330
2331  /**
2332   * Small utility method to save me from typing the hell out of myself in
2333   * paint().
2334   */
2335  private boolean hitClip(Graphics g, Rectangle r)
2336  {
2337    return g.hitClip(r.x, r.y, r.width, r.height);
2338  }
2339}