001    /* BasicScrollBarUI.java --
002       Copyright (C) 2004, 2005, 2006,  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import java.awt.Color;
042    import java.awt.Component;
043    import java.awt.Container;
044    import java.awt.Dimension;
045    import java.awt.Graphics;
046    import java.awt.Insets;
047    import java.awt.LayoutManager;
048    import java.awt.Rectangle;
049    import java.awt.event.ActionEvent;
050    import java.awt.event.ActionListener;
051    import java.awt.event.MouseAdapter;
052    import java.awt.event.MouseEvent;
053    import java.awt.event.MouseMotionListener;
054    import java.beans.PropertyChangeEvent;
055    import java.beans.PropertyChangeListener;
056    
057    import javax.swing.AbstractAction;
058    import javax.swing.ActionMap;
059    import javax.swing.BoundedRangeModel;
060    import javax.swing.InputMap;
061    import javax.swing.JButton;
062    import javax.swing.JComponent;
063    import javax.swing.JScrollBar;
064    import javax.swing.JSlider;
065    import javax.swing.LookAndFeel;
066    import javax.swing.SwingConstants;
067    import javax.swing.SwingUtilities;
068    import javax.swing.Timer;
069    import javax.swing.UIManager;
070    import javax.swing.event.ChangeEvent;
071    import javax.swing.event.ChangeListener;
072    import javax.swing.plaf.ActionMapUIResource;
073    import javax.swing.plaf.ComponentUI;
074    import javax.swing.plaf.ScrollBarUI;
075    
076    /**
077     * The Basic Look and Feel UI delegate for JScrollBar.
078     */
079    public class BasicScrollBarUI extends ScrollBarUI implements LayoutManager,
080                                                                 SwingConstants
081    {
082      /**
083       * A helper class that listens to the two JButtons on each end of the
084       * JScrollBar.
085       */
086      protected class ArrowButtonListener extends MouseAdapter
087      {
088    
089        /**
090         * Move the thumb in the direction specified by the  button's arrow. If
091         * this button is held down, then it should keep moving the thumb.
092         *
093         * @param e The MouseEvent fired by the JButton.
094         */
095        public void mousePressed(MouseEvent e)
096        {
097          scrollTimer.stop();
098          scrollListener.setScrollByBlock(false);
099          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    }