Source for javax.swing.plaf.basic.BasicSliderUI

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