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