OSDN Git Service

400ede03ce9b966b4bc506a7e77be7356e7c013a
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicScrollBarUI.java
1 /* BasicScrollBarUI.java --
2    Copyright (C) 2004, 2005, 2006,  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.plaf.basic;
40
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Container;
44 import java.awt.Dimension;
45 import java.awt.Graphics;
46 import java.awt.Insets;
47 import java.awt.LayoutManager;
48 import java.awt.Rectangle;
49 import java.awt.event.ActionEvent;
50 import java.awt.event.ActionListener;
51 import java.awt.event.MouseAdapter;
52 import java.awt.event.MouseEvent;
53 import java.awt.event.MouseMotionListener;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
56
57 import javax.swing.AbstractAction;
58 import javax.swing.ActionMap;
59 import javax.swing.BoundedRangeModel;
60 import javax.swing.InputMap;
61 import javax.swing.JButton;
62 import javax.swing.JComponent;
63 import javax.swing.JScrollBar;
64 import javax.swing.JSlider;
65 import javax.swing.LookAndFeel;
66 import javax.swing.SwingConstants;
67 import javax.swing.SwingUtilities;
68 import javax.swing.Timer;
69 import javax.swing.UIManager;
70 import javax.swing.event.ChangeEvent;
71 import javax.swing.event.ChangeListener;
72 import javax.swing.plaf.ActionMapUIResource;
73 import javax.swing.plaf.ComponentUI;
74 import javax.swing.plaf.ScrollBarUI;
75
76 /**
77  * The Basic Look and Feel UI delegate for JScrollBar.
78  */
79 public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager,
80                                                              SwingConstants
81 {
82   /**
83    * A helper class that listens to the two JButtons on each end of the
84    * JScrollBar.
85    */
86   protected class ArrowButtonListener extends MouseAdapter
87   {
88    
89     /**
90      * Move the thumb in the direction specified by the  button's arrow. If
91      * this button is held down, then it should keep moving the thumb.
92      *
93      * @param e The MouseEvent fired by the JButton.
94      */
95     public void mousePressed(MouseEvent e)
96     {
97       scrollTimer.stop();
98       scrollListener.setScrollByBlock(false);
99       if (e.getSource() == incrButton)
100           scrollListener.setDirection(POSITIVE_SCROLL);
101       else if (e.getSource() == decrButton)
102           scrollListener.setDirection(NEGATIVE_SCROLL);
103       scrollTimer.setDelay(100);
104       scrollTimer.start();
105     }
106
107     /**
108      * Stops the thumb when the JButton is released.
109      *
110      * @param e The MouseEvent fired by the JButton.
111      */
112     public void mouseReleased(MouseEvent e)
113     {
114       scrollTimer.stop();
115       scrollTimer.setDelay(300);
116       if (e.getSource() == incrButton)
117           scrollByUnit(POSITIVE_SCROLL);
118       else if (e.getSource() == decrButton)
119         scrollByUnit(NEGATIVE_SCROLL);
120     }
121   }
122
123   /**
124    * A helper class that listens to the ScrollBar's model for ChangeEvents.
125    */
126   protected class ModelListener implements ChangeListener
127   {
128     /**
129      * Called when the model changes.
130      *
131      * @param e The ChangeEvent fired by the model.
132      */
133     public void stateChanged(ChangeEvent e)
134     {
135       calculatePreferredSize();
136       updateThumbRect();
137       scrollbar.repaint();
138     }
139   }
140
141   /**
142    * A helper class that listens to the ScrollBar's properties.
143    */
144   public class PropertyChangeHandler implements PropertyChangeListener
145   {
146     /**
147      * Called when one of the ScrollBar's properties change.
148      *
149      * @param e The PropertyChangeEvent fired by the ScrollBar.
150      */
151     public void propertyChange(PropertyChangeEvent e)
152     {
153       if (e.getPropertyName().equals("model"))
154         {
155           ((BoundedRangeModel) e.getOldValue()).removeChangeListener(modelListener);
156           scrollbar.getModel().addChangeListener(modelListener);
157           updateThumbRect();
158         }
159       else if (e.getPropertyName().equals("orientation"))
160         {
161           uninstallListeners();
162           uninstallComponents();
163           uninstallDefaults();
164           installDefaults();
165           installComponents();
166           installListeners();
167         }
168       else if (e.getPropertyName().equals("enabled"))
169         {
170           Boolean b = (Boolean) e.getNewValue();
171           if (incrButton != null)
172             incrButton.setEnabled(b.booleanValue());
173           if (decrButton != null)
174             decrButton.setEnabled(b.booleanValue());
175         }
176     }
177   }
178
179   /**
180    * A helper class that listens for events from the timer that is used to
181    * move the thumb.
182    */
183   protected class ScrollListener implements ActionListener
184   {
185     /** The direction the thumb moves in. */
186     private transient int direction;
187
188     /** Whether movement will be in blocks. */
189     private transient boolean block;
190
191     /**
192      * Creates a new ScrollListener object. The default is scrolling
193      * positively with block movement.
194      */
195     public ScrollListener()
196     {
197       direction = POSITIVE_SCROLL;
198       block = true;
199     }
200
201     /**
202      * Creates a new ScrollListener object using the given direction and
203      * block.
204      *
205      * @param dir The direction to move in.
206      * @param block Whether movement will be in blocks.
207      */
208     public ScrollListener(int dir, boolean block)
209     {
210       direction = dir;
211       this.block = block;
212     }
213
214     /**
215      * Sets the direction to scroll in.
216      *
217      * @param direction The direction to scroll in.
218      */
219     public void setDirection(int direction)
220     {
221       this.direction = direction;
222     }
223
224     /**
225      * Sets whether scrolling will be done in blocks.
226      *
227      * @param block Whether scrolling will be in blocks.
228      */
229     public void setScrollByBlock(boolean block)
230     {
231       this.block = block;
232     }
233
234     /**
235      * Called every time the timer reaches its interval.
236      *
237      * @param e The ActionEvent fired by the timer.
238      */
239     public void actionPerformed(ActionEvent e)
240     {
241       if (block)
242         {
243           // Only need to check it if it's block scrolling
244           // We only block scroll if the click occurs
245           // in the track.
246           if (!trackListener.shouldScroll(direction))
247             {
248               trackHighlight = NO_HIGHLIGHT;
249               scrollbar.repaint();
250               return;
251             }
252             scrollByBlock(direction);
253         }
254       else
255         scrollByUnit(direction);
256     }
257   }
258
259   /**
260    * Helper class that listens for movement on the track.
261    */
262   protected class TrackListener extends MouseAdapter
263     implements MouseMotionListener
264   {
265     /** The current X coordinate of the mouse. */
266     protected int currentMouseX;
267
268     /** The current Y coordinate of the mouse. */
269     protected int currentMouseY;
270
271     /**
272      * The offset between the current mouse cursor and the  current value of
273      * the scrollbar.
274      */
275     protected int offset;
276
277     /**
278      * This method is called when the mouse is being dragged.
279      *
280      * @param e The MouseEvent given.
281      */
282     public void mouseDragged(MouseEvent e)
283     {
284       currentMouseX = e.getX();
285       currentMouseY = e.getY();
286       if (scrollbar.getValueIsAdjusting())
287         {
288           int value;
289           if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
290             value = valueForXPosition(currentMouseX) - offset;
291           else
292             value = valueForYPosition(currentMouseY) - offset;
293
294           scrollbar.setValue(value);
295         }
296     }
297
298     /**
299      * This method is called when the mouse is moved.
300      *
301      * @param e The MouseEvent given.
302      */
303     public void mouseMoved(MouseEvent e)
304     {
305       if (thumbRect.contains(e.getPoint()))
306         thumbRollover = true;
307       else
308         thumbRollover = false;
309     }
310
311     /**
312      * This method is called when the mouse is pressed. When it is pressed,
313      * the thumb should move in blocks towards the cursor.
314      *
315      * @param e The MouseEvent given.
316      */
317     public void mousePressed(MouseEvent e)
318     {
319       currentMouseX = e.getX();
320       currentMouseY = e.getY();
321
322       int value;
323       if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
324         value = valueForXPosition(currentMouseX);
325       else
326         value = valueForYPosition(currentMouseY);
327
328       if (! thumbRect.contains(e.getPoint()))
329         {
330           scrollTimer.stop();
331           scrollListener.setScrollByBlock(true);
332           if (value > scrollbar.getValue())
333             {
334               trackHighlight = INCREASE_HIGHLIGHT;
335               scrollListener.setDirection(POSITIVE_SCROLL);
336             }
337           else
338             {
339               trackHighlight = DECREASE_HIGHLIGHT;
340               scrollListener.setDirection(NEGATIVE_SCROLL);
341             }
342       scrollTimer.setDelay(100);
343           scrollTimer.start();
344         }
345       else
346         {
347           // We'd like to keep track of where the cursor
348           // is inside the thumb.
349           // This works because the scrollbar's value represents 
350           // "lower" edge of the thumb. The value at which
351           // the cursor is at must be greater or equal
352           // to that value.
353
354       scrollListener.setScrollByBlock(false);
355           scrollbar.setValueIsAdjusting(true);
356       offset = value - scrollbar.getValue();
357         }
358       scrollbar.repaint();
359     }
360
361     /**
362      * This method is called when the mouse is released. It should stop
363      * movement on the thumb
364      *
365      * @param e The MouseEvent given.
366      */
367     public void mouseReleased(MouseEvent e)
368     {
369       scrollTimer.stop();
370       scrollTimer.setDelay(300);
371       currentMouseX = e.getX();
372       currentMouseY = e.getY();
373
374       if (shouldScroll(POSITIVE_SCROLL))
375         scrollByBlock(POSITIVE_SCROLL);
376       else if (shouldScroll(NEGATIVE_SCROLL))
377         scrollByBlock(NEGATIVE_SCROLL);
378
379       trackHighlight = NO_HIGHLIGHT;
380       scrollListener.setScrollByBlock(false);
381       scrollbar.setValueIsAdjusting(true);
382       scrollbar.repaint();
383     }
384
385     /**
386      * A helper method that decides whether we should keep scrolling in the
387      * given direction.
388      *
389      * @param direction The direction to check for.
390      *
391      * @return Whether the thumb should keep scrolling.
392      */
393     boolean shouldScroll(int direction)
394     {
395       int value;
396       if (scrollbar.getOrientation() == HORIZONTAL)
397         value = valueForXPosition(currentMouseX);
398       else
399         value = valueForYPosition(currentMouseY);
400
401       if (thumbRect.contains(currentMouseX, currentMouseY))
402         return false;
403       
404       if (direction == POSITIVE_SCROLL)
405         return value > scrollbar.getValue();
406       else
407         return value < scrollbar.getValue();
408     }
409   }
410
411   /** The listener that listens to the JButtons. */
412   protected ArrowButtonListener buttonListener;
413
414   /** The listener that listens to the model. */
415   protected ModelListener modelListener;
416
417   /** The listener that listens to the scrollbar for property changes. */
418   protected PropertyChangeListener propertyChangeListener;
419
420   /** The listener that listens to the timer. */
421   protected ScrollListener scrollListener;
422
423   /** The listener that listens for MouseEvents on the track. */
424   protected TrackListener trackListener;
425
426   /** The JButton that decrements the scrollbar's value. */
427   protected JButton decrButton;
428
429   /** The JButton that increments the scrollbar's value. */
430   protected JButton incrButton;
431
432   /** The dimensions of the maximum thumb size. */
433   protected Dimension maximumThumbSize;
434
435   /** The dimensions of the minimum thumb size. */
436   protected Dimension minimumThumbSize;
437
438   /** The color of the thumb. */
439   protected Color thumbColor;
440
441   /** The outer shadow of the thumb. */
442   protected Color thumbDarkShadowColor;
443
444   /** The top and left edge color for the thumb. */
445   protected Color thumbHighlightColor;
446
447   /** The outer light shadow for the thumb. */
448   protected Color thumbLightShadowColor;
449
450   /** The color that is used when the mouse press occurs in the track. */
451   protected Color trackHighlightColor;
452
453   /** The color of the track. */
454   protected Color trackColor;
455
456   /** The size and position of the track. */
457   protected Rectangle trackRect;
458
459   /** The size and position of the thumb. */
460   protected Rectangle thumbRect;
461
462   /** Indicates that the decrease highlight should be painted. */
463   protected static final int DECREASE_HIGHLIGHT = 1;
464
465   /** Indicates that the increase highlight should be painted. */
466   protected static final int INCREASE_HIGHLIGHT = 2;
467
468   /** Indicates that no highlight should be painted. */
469   protected static final int NO_HIGHLIGHT = 0;
470
471   /** Indicates that the scrolling direction is positive. */
472   private static final int POSITIVE_SCROLL = 1;
473
474   /** Indicates that the scrolling direction is negative. */
475   private static final int NEGATIVE_SCROLL = -1;
476
477   /** The cached preferred size for the scrollbar. */
478   private transient Dimension preferredSize;
479
480   /** The current highlight status. */
481   protected int trackHighlight;
482
483   /** FIXME: Use this for something (presumably mouseDragged) */
484   protected boolean isDragging;
485
486   /** The timer used to move the thumb when the mouse is held. */
487   protected Timer scrollTimer;
488
489   /** The scrollbar this UI is acting for. */
490   protected JScrollBar scrollbar;
491   
492   /** True if the mouse is over the thumb. */
493   boolean thumbRollover;
494
495   /**
496    * This method adds a component to the layout.
497    *
498    * @param name The name to associate with the component that is added.
499    * @param child The Component to add.
500    */
501   public void addLayoutComponent(String name, Component child)
502   {
503     // You should not be adding stuff to this component.
504     // The contents are fixed.
505   }
506
507   /**
508    * This method configures the scrollbar's colors. This can be  done by
509    * looking up the standard colors from the Look and Feel defaults.
510    */
511   protected void configureScrollBarColors()
512   {
513     trackColor = UIManager.getColor("ScrollBar.track");
514     trackHighlightColor = UIManager.getColor("ScrollBar.trackHighlight");
515     thumbColor = UIManager.getColor("ScrollBar.thumb");
516     thumbHighlightColor = UIManager.getColor("ScrollBar.thumbHighlight");
517     thumbDarkShadowColor = UIManager.getColor("ScrollBar.thumbDarkShadow");
518     thumbLightShadowColor = UIManager.getColor("ScrollBar.thumbShadow");
519   }
520
521   /**
522    * This method creates an ArrowButtonListener.
523    *
524    * @return A new ArrowButtonListener.
525    */
526   protected ArrowButtonListener createArrowButtonListener()
527   {
528     return new ArrowButtonListener();
529   }
530
531   /**
532    * This method creates a new JButton with the appropriate icon for the
533    * orientation.
534    *
535    * @param orientation The orientation this JButton uses.
536    *
537    * @return The increase JButton.
538    */
539   protected JButton createIncreaseButton(int orientation)
540   {
541     return new BasicArrowButton(orientation);
542   }
543
544   /**
545    * This method creates a new JButton with the appropriate icon for the
546    * orientation.
547    *
548    * @param orientation The orientation this JButton uses.
549    *
550    * @return The decrease JButton.
551    */
552   protected JButton createDecreaseButton(int orientation)
553   {
554     return new BasicArrowButton(orientation);
555   }
556
557   /**
558    * This method creates a new ModelListener.
559    *
560    * @return A new ModelListener.
561    */
562   protected ModelListener createModelListener()
563   {
564     return new ModelListener();
565   }
566
567   /**
568    * This method creates a new PropertyChangeListener.
569    *
570    * @return A new PropertyChangeListener.
571    */
572   protected PropertyChangeListener createPropertyChangeListener()
573   {
574     return new PropertyChangeHandler();
575   }
576
577   /**
578    * This method creates a new ScrollListener.
579    *
580    * @return A new ScrollListener.
581    */
582   protected ScrollListener createScrollListener()
583   {
584     return new ScrollListener();
585   }
586
587   /**
588    * This method creates a new TrackListener.
589    *
590    * @return A new TrackListener.
591    */
592   protected TrackListener createTrackListener()
593   {
594     return new TrackListener();
595   }
596
597   /**
598    * This method returns a new BasicScrollBarUI.
599    *
600    * @param c The JComponent to create a UI for.
601    *
602    * @return A new BasicScrollBarUI.
603    */
604   public static ComponentUI createUI(JComponent c)
605   {
606     return new BasicScrollBarUI();
607   }
608
609   /**
610    * This method returns the maximum size for this JComponent.
611    *
612    * @param c The JComponent to measure the maximum size for.
613    *
614    * @return The maximum size for the component.
615    */
616   public Dimension getMaximumSize(JComponent c)
617   {
618     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
619   }
620
621   /**
622    * This method returns the maximum thumb size.
623    *
624    * @return The maximum thumb size.
625    */
626   protected Dimension getMaximumThumbSize()
627   {
628     return maximumThumbSize;
629   }
630
631   /**
632    * This method returns the minimum size for this JComponent.
633    *
634    * @param c The JComponent to measure the minimum size for.
635    *
636    * @return The minimum size for the component.
637    */
638   public Dimension getMinimumSize(JComponent c)
639   {
640     return getPreferredSize(c);
641   }
642
643   /**
644    * This method returns the minimum thumb size.
645    *
646    * @return The minimum thumb size.
647    */
648   protected Dimension getMinimumThumbSize()
649   {
650     return minimumThumbSize;
651   }
652
653   /**
654    * This method calculates the preferred size since calling
655    * getPreferredSize() returns a cached value.
656    * This is package-private to avoid an accessor method.
657    */
658   void calculatePreferredSize()
659   {
660     int height;
661     int width;
662     height = width = 0;
663
664     if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
665       {
666         width += incrButton.getPreferredSize().getWidth();
667         width += decrButton.getPreferredSize().getWidth();
668         width += 16;
669         height = UIManager.getInt("ScrollBar.width");
670       }
671     else
672       {
673         height += incrButton.getPreferredSize().getHeight();
674         height += decrButton.getPreferredSize().getHeight();
675         height += 16;
676         width = UIManager.getInt("ScrollBar.width");
677       }
678
679     Insets insets = scrollbar.getInsets();
680
681     height += insets.top + insets.bottom;
682     width += insets.left + insets.right;
683
684     preferredSize = new Dimension(width, height);
685   }
686
687   /**
688    * This method returns a cached value of the preferredSize. The only
689    * restrictions are: If the scrollbar is horizontal, the height should be
690    * the maximum of the height of the JButtons and  the minimum width of the
691    * thumb. For vertical scrollbars, the  calculation is similar (swap width
692    * for height and vice versa).
693    *
694    * @param c The JComponent to measure.
695    *
696    * @return The preferredSize.
697    */
698   public Dimension getPreferredSize(JComponent c)
699   {
700     calculatePreferredSize();
701     return preferredSize;
702   }
703
704   /**
705    * This method returns the thumb's bounds based on the  current value of the
706    * scrollbar. This method updates the cached value and returns that.
707    *
708    * @return The thumb bounds.
709    */
710   protected Rectangle getThumbBounds()
711   {
712     return thumbRect;
713   }
714
715   /**
716    * This method calculates the bounds of the track. This method updates the
717    * cached value and returns it.
718    *
719    * @return The track's bounds.
720    */
721   protected Rectangle getTrackBounds()
722   {
723     return trackRect;
724   }
725
726   /**
727    * This method installs any addition Components that  are a part of or
728    * related to this scrollbar.
729    */
730   protected void installComponents()
731   {
732     int orientation = scrollbar.getOrientation();
733     switch (orientation)
734       {
735       case JScrollBar.HORIZONTAL:
736         incrButton = createIncreaseButton(EAST);
737         decrButton = createDecreaseButton(WEST);
738         break;
739       default:
740         incrButton = createIncreaseButton(SOUTH);
741         decrButton = createDecreaseButton(NORTH);
742         break;
743       }
744
745     if (incrButton != null)
746       scrollbar.add(incrButton);
747     if (decrButton != null)
748       scrollbar.add(decrButton);
749   }
750
751   /**
752    * This method installs the defaults for the scrollbar specified by the
753    * Basic Look and Feel.
754    */
755   protected void installDefaults()
756   {
757     LookAndFeel.installColors(scrollbar, "ScrollBar.background",
758                               "ScrollBar.foreground");
759     LookAndFeel.installBorder(scrollbar, "ScrollBar.border");
760     scrollbar.setOpaque(true);
761     scrollbar.setLayout(this);
762
763     configureScrollBarColors();
764
765     maximumThumbSize = UIManager.getDimension("ScrollBar.maximumThumbSize");
766     minimumThumbSize = UIManager.getDimension("ScrollBar.minimumThumbSize");
767   }
768
769   /**
770    * Installs the input map from the look and feel defaults, and a 
771    * corresponding action map.  Note the the keyboard bindings will only
772    * work when the {@link JScrollBar} component has the focus, which is rare.
773    */
774   protected void installKeyboardActions()
775   {
776     InputMap keyMap = getInputMap(
777         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
778     SwingUtilities.replaceUIInputMap(scrollbar, 
779         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
780     ActionMap map = getActionMap();
781     SwingUtilities.replaceUIActionMap(scrollbar, map);
782   }
783
784   /**
785    * Uninstalls the input map and action map installed by
786    * {@link #installKeyboardActions()}.
787    */
788   protected void uninstallKeyboardActions()
789   {
790     SwingUtilities.replaceUIActionMap(scrollbar, null);
791     SwingUtilities.replaceUIInputMap(scrollbar, 
792         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
793   }
794
795   InputMap getInputMap(int condition) 
796   {
797     if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
798       return (InputMap) UIManager.get("ScrollBar.focusInputMap");
799     return null;
800   }
801   
802   /**
803    * Returns the action map for the {@link JScrollBar}.  All scroll bars 
804    * share a single action map which is created the first time this method is 
805    * called, then stored in the UIDefaults table for subsequent access.
806    * 
807    * @return The shared action map.
808    */
809   ActionMap getActionMap() 
810   {
811     ActionMap map = (ActionMap) UIManager.get("ScrollBar.actionMap");
812
813     if (map == null) // first time here
814       {
815         map = createActionMap();
816         if (map != null)
817           UIManager.put("ScrollBar.actionMap", map);
818       }
819     return map;
820   }
821
822   /**
823    * Creates the action map shared by all {@link JSlider} instances.
824    * This method is called once by {@link #getActionMap()} when it 
825    * finds no action map in the UIDefaults table...after the map is 
826    * created, it gets added to the defaults table so that subsequent 
827    * calls to {@link #getActionMap()} will return the same shared 
828    * instance.
829    * 
830    * @return The action map.
831    */
832   ActionMap createActionMap()
833   {
834     ActionMap map = new ActionMapUIResource();
835     map.put("positiveUnitIncrement", 
836             new AbstractAction("positiveUnitIncrement") {
837               public void actionPerformed(ActionEvent event)
838               {
839                 JScrollBar sb = (JScrollBar) event.getSource();
840                 if (sb.isVisible()) 
841                   {
842                     int delta = sb.getUnitIncrement(1);
843                     sb.setValue(sb.getValue() + delta);
844                   }
845               }
846             }
847     );
848     map.put("positiveBlockIncrement", 
849             new AbstractAction("positiveBlockIncrement") {
850               public void actionPerformed(ActionEvent event)
851               {
852                 JScrollBar sb = (JScrollBar) event.getSource();
853                 if (sb.isVisible()) 
854                   {
855                     int delta = sb.getBlockIncrement(1);
856                     sb.setValue(sb.getValue() + delta);
857                   }
858               }
859             }
860     );
861     map.put("negativeUnitIncrement", 
862             new AbstractAction("negativeUnitIncrement") {
863               public void actionPerformed(ActionEvent event)
864               {
865                 JScrollBar sb = (JScrollBar) event.getSource();
866                 if (sb.isVisible()) 
867                   {
868                     int delta = sb.getUnitIncrement(-1);
869                     sb.setValue(sb.getValue() + delta);
870                   }
871               }
872             }
873     );
874     map.put("negativeBlockIncrement", 
875             new AbstractAction("negativeBlockIncrement") {
876               public void actionPerformed(ActionEvent event)
877               {
878                 JScrollBar sb = (JScrollBar) event.getSource();
879                 if (sb.isVisible()) 
880                   {
881                     int delta = sb.getBlockIncrement(-1);
882                     sb.setValue(sb.getValue() + delta);
883                   }
884               }
885             }
886     );
887     map.put("minScroll", 
888             new AbstractAction("minScroll") {
889               public void actionPerformed(ActionEvent event)
890               {
891                 JScrollBar sb = (JScrollBar) event.getSource();
892                 if (sb.isVisible()) 
893                   {
894                     sb.setValue(sb.getMinimum());
895                   }
896               }
897             }
898     );
899     map.put("maxScroll", 
900             new AbstractAction("maxScroll") {
901               public void actionPerformed(ActionEvent event)
902               {
903                 JScrollBar sb = (JScrollBar) event.getSource();
904                 if (sb.isVisible()) 
905                   {
906                     sb.setValue(sb.getMaximum());
907                   }
908               }
909             }
910     );
911     return map;
912   }
913   
914   /**
915    * This method installs any listeners for the scrollbar. This method also
916    * installs listeners for things such as the JButtons and the timer.
917    */
918   protected void installListeners()
919   {
920     scrollListener = createScrollListener();
921     trackListener = createTrackListener();
922     buttonListener = createArrowButtonListener();
923     modelListener = createModelListener();
924     propertyChangeListener = createPropertyChangeListener();
925
926     scrollbar.addMouseMotionListener(trackListener);
927     scrollbar.addMouseListener(trackListener);
928
929     incrButton.addMouseListener(buttonListener);
930     decrButton.addMouseListener(buttonListener);
931
932     scrollbar.addPropertyChangeListener(propertyChangeListener);
933     scrollbar.getModel().addChangeListener(modelListener);
934
935     scrollTimer.addActionListener(scrollListener);
936   }
937
938   /**
939    * This method installs the UI for the component. This can include setting
940    * up listeners, defaults,  and components. This also includes initializing
941    * any data objects.
942    *
943    * @param c The JComponent to install.
944    */
945   public void installUI(JComponent c)
946   {
947     super.installUI(c);
948     if (c instanceof JScrollBar)
949       {
950         scrollbar = (JScrollBar) c;
951
952         trackRect = new Rectangle();
953         thumbRect = new Rectangle();
954
955         scrollTimer = new Timer(300, null);
956
957         installDefaults();
958         installComponents();
959         configureScrollBarColors();
960         installListeners();
961         installKeyboardActions();
962
963         calculatePreferredSize();
964       }
965   }
966
967   /**
968    * This method lays out the scrollbar.
969    *
970    * @param scrollbarContainer The Container to layout.
971    */
972   public void layoutContainer(Container scrollbarContainer)
973   {
974     if (scrollbarContainer instanceof JScrollBar)
975       {
976         if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
977           layoutHScrollbar((JScrollBar) scrollbarContainer);
978         else
979           layoutVScrollbar((JScrollBar) scrollbarContainer);
980       }
981   }
982
983   /**
984    * This method lays out the scrollbar horizontally.
985    *
986    * @param sb The JScrollBar to layout.
987    */
988   protected void layoutHScrollbar(JScrollBar sb)
989   {
990     Rectangle vr = new Rectangle();
991     SwingUtilities.calculateInnerArea(scrollbar, vr);
992
993     Dimension incrDims = incrButton.getPreferredSize();
994     Dimension decrDims = decrButton.getPreferredSize();
995     
996     // calculate and update the track bounds
997     SwingUtilities.calculateInnerArea(scrollbar, trackRect);
998     trackRect.width -= incrDims.getWidth();
999     trackRect.width -= decrDims.getWidth();
1000     trackRect.x += decrDims.getWidth();
1001
1002     updateThumbRect();
1003     
1004     decrButton.setBounds(vr.x, vr.y, decrDims.width, trackRect.height);
1005     incrButton.setBounds(trackRect.x + trackRect.width, vr.y, incrDims.width,
1006                          trackRect.height);
1007   }
1008
1009   /**
1010    * This method lays out the scrollbar vertically.
1011    *
1012    * @param sb The JScrollBar to layout.
1013    */
1014   protected void layoutVScrollbar(JScrollBar sb)
1015   {
1016     Rectangle vr = new Rectangle();
1017     SwingUtilities.calculateInnerArea(scrollbar, vr);
1018
1019     Dimension incrDims = incrButton.getPreferredSize();
1020     Dimension decrDims = decrButton.getPreferredSize();
1021     
1022     // Update rectangles
1023     SwingUtilities.calculateInnerArea(scrollbar, trackRect);
1024     trackRect.height -= incrDims.getHeight();
1025     trackRect.height -= decrDims.getHeight();
1026     trackRect.y += decrDims.getHeight();
1027     
1028     updateThumbRect();
1029
1030     decrButton.setBounds(vr.x, vr.y, trackRect.width, decrDims.height);
1031     incrButton.setBounds(vr.x, trackRect.y + trackRect.height,
1032                          trackRect.width, incrDims.height);
1033   }
1034
1035   /**
1036    * Updates the thumb rect.
1037    */
1038   void updateThumbRect()
1039   {
1040     int max = scrollbar.getMaximum();
1041     int min = scrollbar.getMinimum();
1042     int value = scrollbar.getValue();
1043     int extent = scrollbar.getVisibleAmount();
1044     if (max - extent <= min)
1045       {
1046         if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL)
1047           {
1048             thumbRect.x = trackRect.x;
1049             thumbRect.y = trackRect.y;
1050             thumbRect.width = getMinimumThumbSize().width;
1051             thumbRect.height = trackRect.height;
1052           }
1053         else
1054           {
1055             thumbRect.x = trackRect.x;
1056             thumbRect.y = trackRect.y;
1057             thumbRect.width = trackRect.width;
1058             thumbRect.height = getMinimumThumbSize().height;
1059           }
1060       }
1061     else
1062       {
1063         if (scrollbar.getOrientation() == JScrollBar.HORIZONTAL)
1064           {
1065             thumbRect.x = trackRect.x;
1066             thumbRect.width = Math.max(extent * trackRect.width / (max - min),
1067                 getMinimumThumbSize().width);
1068             int availableWidth = trackRect.width - thumbRect.width;
1069             thumbRect.x += (value - min) * availableWidth / (max - min - extent);
1070             thumbRect.y = trackRect.y;
1071             thumbRect.height = trackRect.height;
1072           }
1073         else
1074           {
1075             thumbRect.x = trackRect.x;
1076             thumbRect.height = Math.max(extent * trackRect.height / (max - min),
1077                     getMinimumThumbSize().height);
1078             int availableHeight = trackRect.height - thumbRect.height;
1079             thumbRect.y = trackRect.y 
1080               + (value - min) * availableHeight / (max - min - extent);
1081             thumbRect.width = trackRect.width;
1082           }
1083       }
1084
1085   }
1086   
1087   /**
1088    * This method returns the minimum size required for the layout.
1089    *
1090    * @param scrollbarContainer The Container that is laid out.
1091    *
1092    * @return The minimum size.
1093    */
1094   public Dimension minimumLayoutSize(Container scrollbarContainer)
1095   {
1096     return preferredLayoutSize(scrollbarContainer);
1097   }
1098
1099   /**
1100    * This method is called when the component is painted.
1101    *
1102    * @param g The Graphics object to paint with.
1103    * @param c The JComponent to paint.
1104    */
1105   public void paint(Graphics g, JComponent c)
1106   {
1107     paintTrack(g, c, getTrackBounds());
1108     paintThumb(g, c, getThumbBounds());
1109
1110     if (trackHighlight == INCREASE_HIGHLIGHT)
1111       paintIncreaseHighlight(g);
1112     else if (trackHighlight == DECREASE_HIGHLIGHT)
1113       paintDecreaseHighlight(g);
1114   }
1115
1116   /**
1117    * This method is called when repainting and the mouse is  pressed in the
1118    * track. It paints the track below the thumb with the trackHighlight
1119    * color.
1120    *
1121    * @param g The Graphics object to paint with.
1122    */
1123   protected void paintDecreaseHighlight(Graphics g)
1124   {
1125     Color saved = g.getColor();
1126
1127     g.setColor(trackHighlightColor);
1128     if (scrollbar.getOrientation() == HORIZONTAL)
1129       g.fillRect(trackRect.x, trackRect.y, thumbRect.x - trackRect.x,
1130                  trackRect.height);
1131     else
1132       g.fillRect(trackRect.x, trackRect.y, trackRect.width,
1133                  thumbRect.y - trackRect.y);
1134     g.setColor(saved);
1135   }
1136
1137   /**
1138    * This method is called when repainting and the mouse is  pressed in the
1139    * track. It paints the track above the thumb with the trackHighlight
1140    * color.
1141    *
1142    * @param g The Graphics objet to paint with.
1143    */
1144   protected void paintIncreaseHighlight(Graphics g)
1145   {
1146     Color saved = g.getColor();
1147
1148     g.setColor(trackHighlightColor);
1149     if (scrollbar.getOrientation() == HORIZONTAL)
1150       g.fillRect(thumbRect.x + thumbRect.width, trackRect.y,
1151                  trackRect.x + trackRect.width - thumbRect.x - thumbRect.width,
1152                  trackRect.height);
1153     else
1154       g.fillRect(trackRect.x, thumbRect.y + thumbRect.height, trackRect.width,
1155                  trackRect.y + trackRect.height - thumbRect.y
1156                  - thumbRect.height);
1157     g.setColor(saved);
1158   }
1159
1160   /**
1161    * This method paints the thumb.
1162    *
1163    * @param g The Graphics object to paint with.
1164    * @param c The Component that is being painted.
1165    * @param thumbBounds The thumb bounds.
1166    */
1167   protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)
1168   {
1169     g.setColor(thumbColor);
1170     g.fillRect(thumbBounds.x, thumbBounds.y, thumbBounds.width,
1171                thumbBounds.height);
1172
1173     BasicGraphicsUtils.drawBezel(g, thumbBounds.x, thumbBounds.y,
1174                                  thumbBounds.width, thumbBounds.height,
1175                                  false, false, thumbDarkShadowColor,
1176                                  thumbDarkShadowColor, thumbHighlightColor,
1177                                  thumbHighlightColor);
1178   }
1179
1180   /**
1181    * This method paints the track.
1182    *
1183    * @param g The Graphics object to paint with.
1184    * @param c The JComponent being painted.
1185    * @param trackBounds The track's bounds.
1186    */
1187   protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds)
1188   {
1189     Color saved = g.getColor();
1190     g.setColor(trackColor);
1191     g.fill3DRect(trackBounds.x, trackBounds.y, trackBounds.width,
1192                  trackBounds.height, false);
1193     g.setColor(saved);
1194   }
1195
1196   /**
1197    * This method returns the preferred size for the layout.
1198    *
1199    * @param scrollbarContainer The Container to find a size for.
1200    *
1201    * @return The preferred size for the layout.
1202    */
1203   public Dimension preferredLayoutSize(Container scrollbarContainer)
1204   {
1205     if (scrollbarContainer instanceof JComponent)
1206       return getPreferredSize((JComponent) scrollbarContainer);
1207     else
1208       return null;
1209   }
1210
1211   /**
1212    * This method removes a child component from the layout.
1213    *
1214    * @param child The child to remove.
1215    */
1216   public void removeLayoutComponent(Component child)
1217   {
1218     // You should not be removing stuff from this component.
1219   }
1220
1221   /**
1222    * The method scrolls the thumb by a block in the  direction specified.
1223    *
1224    * @param direction The direction to scroll.
1225    */
1226   protected void scrollByBlock(int direction)
1227   {
1228     scrollByBlock(scrollbar, direction);
1229   }
1230
1231   /**
1232    * Scrolls the specified <code>scrollBar</code> by one block (according
1233    * to the scrollable protocol) in the specified <code>direction</code>.
1234    *
1235    * This method is here statically to support wheel scrolling from the
1236    * BasicScrollPaneUI without code duplication.
1237    *
1238    * @param scrollBar the scrollbar to scroll
1239    * @param direction the scroll direction
1240    */
1241   static final void scrollByBlock(JScrollBar scrollBar, int direction)
1242   {
1243     int delta;
1244     if (direction > 0)
1245       delta = scrollBar.getBlockIncrement(direction);
1246     else
1247       delta = - scrollBar.getBlockIncrement(direction);
1248     int oldValue = scrollBar.getValue();
1249     int newValue = oldValue + delta;
1250
1251     // Overflow check.
1252     if (delta > 0 && newValue < oldValue)
1253       newValue = scrollBar.getMaximum();
1254     else if (delta < 0 && newValue > oldValue)
1255       newValue = scrollBar.getMinimum();
1256
1257     scrollBar.setValue(newValue);
1258   }
1259
1260   /**
1261    * The method scrolls the thumb by a unit in the direction specified.
1262    *
1263    * @param direction The direction to scroll.
1264    */
1265   protected void scrollByUnit(int direction)
1266   {
1267     scrollByUnits(scrollbar, direction, 1);
1268   }
1269
1270   /**
1271    * Scrolls the specified <code>scrollbac/code> by <code>units</code> units
1272    * in the specified <code>direction</code>.
1273    *
1274    * This method is here statically to support wheel scrolling from the
1275    * BasicScrollPaneUI without code duplication.
1276    *
1277    * @param scrollBar the scrollbar to scroll
1278    * @param direction the direction
1279    * @param units the number of units to scroll
1280    */
1281   static final void scrollByUnits(JScrollBar scrollBar, int direction,
1282                                    int units)
1283   {
1284     // Do this inside a loop so that we don't clash with the scrollable
1285     // interface, which can return different units at times. For instance,
1286     // a Scrollable could return a unit of 2 pixels only to adjust the
1287     // visibility of an item. If we would simply multiply this by units,
1288     // then we would only get 6 pixels, which is complete crap.
1289     for (int i = 0; i < units; i++)
1290       {
1291         int delta;
1292         if (direction > 0)
1293           delta = scrollBar.getUnitIncrement(direction);
1294         else
1295           delta = - scrollBar.getUnitIncrement(direction);
1296         int oldValue = scrollBar.getValue();
1297         int newValue = oldValue + delta;
1298
1299         // Overflow check.
1300         if (delta > 0 && newValue < oldValue)
1301           newValue = scrollBar.getMaximum();
1302         else if (delta < 0 && newValue > oldValue)
1303           newValue = scrollBar.getMinimum();
1304
1305         scrollBar.setValue(newValue);
1306       }
1307   }
1308
1309   /**
1310    * This method sets the thumb's bounds.
1311    *
1312    * @param x The X position of the thumb.
1313    * @param y The Y position of the thumb.
1314    * @param width The width of the thumb.
1315    * @param height The height of the thumb.
1316    */
1317   protected void setThumbBounds(int x, int y, int width, int height)
1318   {
1319     thumbRect.x = x;
1320     thumbRect.y = y;
1321     thumbRect.width = width;
1322     thumbRect.height = height;
1323   }
1324
1325   /**
1326    * This method uninstalls any components that  are a part of or related to
1327    * this scrollbar.
1328    */
1329   protected void uninstallComponents()
1330   {
1331     if (incrButton != null)
1332       scrollbar.remove(incrButton);
1333     if (decrButton != null)
1334       scrollbar.remove(decrButton);
1335   }
1336
1337   /**
1338    * This method uninstalls any defaults that this scrollbar acquired from the
1339    * Basic Look and Feel defaults.
1340    */
1341   protected void uninstallDefaults()
1342   {
1343     scrollbar.setForeground(null);
1344     scrollbar.setBackground(null);
1345     LookAndFeel.uninstallBorder(scrollbar);
1346     incrButton = null;
1347     decrButton = null;
1348   }
1349
1350   /**
1351    * This method uninstalls any listeners that were registered during install.
1352    */
1353   protected void uninstallListeners()
1354   {
1355     if (scrollTimer != null)
1356       scrollTimer.removeActionListener(scrollListener);
1357
1358     if (scrollbar != null)
1359       {
1360         scrollbar.getModel().removeChangeListener(modelListener);
1361         scrollbar.removePropertyChangeListener(propertyChangeListener);
1362         scrollbar.removeMouseListener(trackListener);
1363         scrollbar.removeMouseMotionListener(trackListener);
1364       }
1365
1366     if (decrButton != null)
1367       decrButton.removeMouseListener(buttonListener);
1368     if (incrButton != null)
1369       incrButton.removeMouseListener(buttonListener);
1370     
1371     propertyChangeListener = null;
1372     modelListener = null;
1373     buttonListener = null;
1374     trackListener = null;
1375     scrollListener = null;
1376   }
1377
1378   /**
1379    * This method uninstalls the UI. This includes removing any defaults,
1380    * listeners, and components that this UI may have initialized. It also
1381    * nulls any instance data.
1382    *
1383    * @param c The Component to uninstall for.
1384    */
1385   public void uninstallUI(JComponent c)
1386   {
1387     uninstallKeyboardActions();
1388     uninstallListeners();
1389     uninstallDefaults();
1390     uninstallComponents();
1391
1392     scrollTimer = null;
1393
1394     thumbRect = null;
1395     trackRect = null;
1396
1397     trackColor = null;
1398     trackHighlightColor = null;
1399     thumbColor = null;
1400     thumbHighlightColor = null;
1401     thumbDarkShadowColor = null;
1402     thumbLightShadowColor = null;
1403
1404     scrollbar = null;
1405   }
1406
1407   /**
1408    * This method returns the value in the scrollbar's range given the y
1409    * coordinate. If the value is out of range, it will return the closest
1410    * legal value.
1411    * This is package-private to avoid an accessor method.
1412    *
1413    * @param yPos The y coordinate to calculate a value for.
1414    *
1415    * @return The value for the y coordinate.
1416    */
1417   int valueForYPosition(int yPos)
1418   {
1419     int min = scrollbar.getMinimum();
1420     int max = scrollbar.getMaximum();
1421     int len = trackRect.height;
1422
1423     int value;
1424
1425     // If the length is 0, you shouldn't be able to even see where the thumb is.
1426     // This really shouldn't ever happen, but just in case, we'll return the middle.
1427     if (len == 0)
1428       return (max - min) / 2;
1429
1430     value = (yPos - trackRect.y) * (max - min) / len + min;
1431
1432     // If this isn't a legal value, then we'll have to move to one now.
1433     if (value > max)
1434       value = max;
1435     else if (value < min)
1436       value = min;
1437     return value;
1438   }
1439
1440   /**
1441    * This method returns the value in the scrollbar's range given the x
1442    * coordinate. If the value is out of range, it will return the closest
1443    * legal value.
1444    * This is package-private to avoid an accessor method.
1445    *
1446    * @param xPos The x coordinate to calculate a value for.
1447    *
1448    * @return The value for the x coordinate.
1449    */
1450   int valueForXPosition(int xPos)
1451   {
1452     int min = scrollbar.getMinimum();
1453     int max = scrollbar.getMaximum();
1454     int len = trackRect.width;
1455
1456     int value;
1457
1458     // If the length is 0, you shouldn't be able to even see where the slider is.
1459     // This really shouldn't ever happen, but just in case, we'll return the middle.
1460     if (len == 0)
1461       return (max - min) / 2;
1462
1463     value = (xPos - trackRect.x) * (max - min) / len + min;
1464
1465     // If this isn't a legal value, then we'll have to move to one now.
1466     if (value > max)
1467       value = max;
1468     else if (value < min)
1469       value = min;
1470     return value;
1471   }
1472   
1473   /**
1474    * Returns true if the mouse is over the thumb.
1475    * 
1476    * @return true if the mouse is over the thumb.
1477    * 
1478    * @since 1.5
1479    */
1480   public boolean isThumbRollover()
1481   {
1482    return thumbRollover; 
1483   }
1484   
1485   /**
1486    * Set thumbRollover to active. This indicates
1487    * whether or not the mouse is over the thumb.
1488    * 
1489    * @param active - true if the mouse is over the thumb.
1490    * 
1491    * @since 1.5
1492    */
1493   protected void setThumbRollover(boolean active)
1494   {
1495     thumbRollover = active;
1496   }
1497   
1498   /**
1499    * Indicates whether the user can position the thumb with 
1500    * a mouse click (i.e. middle button).
1501    * 
1502    * @return true if the user can position the thumb with a mouse
1503    * click.
1504    * 
1505    * @since 1.5
1506    */
1507   public boolean getSupportsAbsolutePositioning()
1508   {
1509     // The positioning feature has not been implemented.
1510     // So, false is always returned.
1511     return false;
1512   }
1513 }