OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicSliderUI.java
1 /* BasicSliderUI.java --
2    Copyright (C) 2004, 2005, 2006,  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.plaf.basic;
40
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Dimension;
44 import java.awt.Graphics;
45 import java.awt.Insets;
46 import java.awt.Point;
47 import java.awt.Polygon;
48 import java.awt.Rectangle;
49 import java.awt.event.ActionEvent;
50 import java.awt.event.ActionListener;
51 import java.awt.event.ComponentAdapter;
52 import java.awt.event.ComponentEvent;
53 import java.awt.event.ComponentListener;
54 import java.awt.event.FocusEvent;
55 import java.awt.event.FocusListener;
56 import java.awt.event.MouseEvent;
57 import java.beans.PropertyChangeEvent;
58 import java.beans.PropertyChangeListener;
59 import java.util.Dictionary;
60 import java.util.Enumeration;
61
62 import javax.swing.AbstractAction;
63 import javax.swing.ActionMap;
64 import javax.swing.BoundedRangeModel;
65 import javax.swing.InputMap;
66 import javax.swing.JComponent;
67 import javax.swing.JSlider;
68 import javax.swing.LookAndFeel;
69 import javax.swing.SwingUtilities;
70 import javax.swing.Timer;
71 import javax.swing.UIManager;
72 import javax.swing.event.ChangeEvent;
73 import javax.swing.event.ChangeListener;
74 import javax.swing.event.MouseInputAdapter;
75 import javax.swing.plaf.ActionMapUIResource;
76 import javax.swing.plaf.ComponentUI;
77 import javax.swing.plaf.SliderUI;
78
79 /**
80  * <p>
81  * BasicSliderUI.java This is the UI delegate in the Basic look and feel that
82  * paints JSliders.
83  * </p>
84  * 
85  * <p>
86  * The UI delegate keeps track of 6 rectangles that place the various parts of
87  * the JSlider inside the component.
88  * </p>
89  * 
90  * <p>
91  * The rectangles are organized as follows:
92  * </p>
93  * <pre>
94  *     +-------------------------------------------------------+ <-- focusRect
95  *     |                                                       |
96  *     |  +==+-------------------+==+--------------------+==+<------ contentRect
97  *     |  |  |                   |  |<---thumbRect       |  |  |
98  *     |  |  |    TRACK          |  |                    |<--------- trackRect
99  *     |  |  +-------------------+==+--------------------+  |  |
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  */
139 public 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 }