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