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