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