OSDN Git Service

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