Source for javax.swing.plaf.basic.BasicSliderUI

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