Source for javax.swing.plaf.basic.BasicSliderUI

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