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