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