Merge from the pain train
[official-gcc.git] / libjava / javax / swing / plaf / basic / BasicSliderUI.java
bloba08acc9bdade15ed8160c3cd20c56cd4e01fde72
1 /* BasicSliderUI.java --
2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
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.
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. */
39 package javax.swing.plaf.basic;
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.ComponentOrientation;
44 import java.awt.Dimension;
45 import java.awt.Graphics;
46 import java.awt.Insets;
47 import java.awt.Point;
48 import java.awt.Polygon;
49 import java.awt.Rectangle;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.awt.event.ComponentAdapter;
53 import java.awt.event.ComponentEvent;
54 import java.awt.event.ComponentListener;
55 import java.awt.event.FocusEvent;
56 import java.awt.event.FocusListener;
57 import java.awt.event.MouseEvent;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
60 import java.util.Dictionary;
61 import java.util.Enumeration;
63 import javax.swing.BoundedRangeModel;
64 import javax.swing.JComponent;
65 import javax.swing.JLabel;
66 import javax.swing.JSlider;
67 import javax.swing.SwingUtilities;
68 import javax.swing.Timer;
69 import javax.swing.UIDefaults;
70 import javax.swing.UIManager;
71 import javax.swing.event.ChangeEvent;
72 import javax.swing.event.ChangeListener;
73 import javax.swing.event.MouseInputAdapter;
74 import javax.swing.plaf.ComponentUI;
75 import javax.swing.plaf.SliderUI;
77 /**
78 * <p>
79 * BasicSliderUI.java This is the UI delegate in the Basic look and feel that
80 * paints JSliders.
81 * </p>
83 * <p>
84 * The UI delegate keeps track of 6 rectangles that place the various parts of
85 * the JSlider inside the component.
86 * </p>
88 * <p>
89 * The rectangles are organized as follows:
90 * </p>
91 * <pre>
92 * +-------------------------------------------------------+ <-- focusRect
93 * | |
94 * | +==+-------------------+==+--------------------+==+<------ contentRect
95 * | | | | |<---thumbRect | | |
96 * | | | TRACK | | |<--------- trackRect
97 * | | +-------------------+==+--------------------+ | |
98 * | | | | | |
99 * | | | TICKS GO HERE |<-------- tickRect
100 * | | | | | |
101 * | +==+-------------------------------------------+==+ |
102 * | | | | | |
103 * | | | | |<----- labelRect
104 * | | | LABELS GO HERE | | |
105 * | | | | | |
106 * | | | | | |
107 * | | | | | |
108 * | | | | | |
109 * | | | | |
110 * </pre>
112 * <p>
113 * The space between the contentRect and the focusRect are the FocusInsets.
114 * </p>
116 * <p>
117 * The space between the focusRect and the component bounds is the insetCache
118 * which are the component's insets.
119 * </p>
121 * <p>
122 * The top of the thumb is the top of the contentRect. The trackRect has to be
123 * as tall as the thumb.
124 * </p>
126 * <p>
127 * The trackRect and tickRect do not start from the left edge of the
128 * focusRect. They are trackBuffer away from each side of the focusRect. This
129 * is so that the thumb has room to move.
130 * </p>
132 * <p>
133 * The labelRect does start right against the contentRect's left and right
134 * edges and it gets all remaining space.
135 * </p>
137 public class BasicSliderUI extends SliderUI
140 * Helper class that listens to the {@link JSlider}'s model for changes.
142 public class ChangeHandler implements ChangeListener
145 * Called when the slider's model has been altered. The UI delegate should
146 * recalculate any rectangles that are dependent on the model for their
147 * positions and repaint.
149 * @param e A static {@link ChangeEvent} passed from the model.
151 public void stateChanged(ChangeEvent e)
153 // Maximum, minimum, and extent values will be taken
154 // care of automatically when the slider is repainted.
155 // Only thing that needs recalculation is the thumb.
156 calculateThumbLocation();
157 slider.repaint();
162 * Helper class that listens for resize events.
164 protected class ComponentHandler extends ComponentAdapter
167 * Called when the size of the component changes. The UI delegate should
168 * recalculate any rectangles that are dependent on the model for their
169 * positions and repaint.
171 * @param e A {@link ComponentEvent}.
173 public void componentResized(ComponentEvent e)
175 calculateGeometry();
177 slider.revalidate();
178 slider.repaint();
183 * Helper class that listens for focus events.
185 public class FocusHandler implements FocusListener
188 * Called when the {@link JSlider} has gained focus. It should repaint
189 * the slider with the focus drawn.
191 * @param e A {@link FocusEvent}.
193 public void focusGained(FocusEvent e)
195 // FIXME: implement.
199 * Called when the {@link JSlider} has lost focus. It should repaint the
200 * slider without the focus drawn.
202 * @param e A {@link FocusEvent}.
204 public void focusLost(FocusEvent e)
206 // FIXME: implement.
211 * Helper class that listens for changes to the properties of the {@link
212 * JSlider}.
214 public class PropertyChangeHandler implements PropertyChangeListener
217 * Called when one of the properties change. The UI should recalculate any
218 * rectangles if necessary and repaint.
220 * @param e A {@link PropertyChangeEvent}.
222 public void propertyChange(PropertyChangeEvent e)
224 // Check for orientation changes.
225 if (e.getPropertyName().equals("orientation"))
226 recalculateIfOrientationChanged();
227 else if (e.getPropertyName().equals("model"))
229 BoundedRangeModel oldModel = (BoundedRangeModel) e.getOldValue();
230 oldModel.removeChangeListener(changeListener);
231 slider.getModel().addChangeListener(changeListener);
232 calculateThumbLocation();
235 // elif the componentOrientation changes (this is a bound property,
236 // just undocumented) we change leftToRightCache. In Sun's
237 // implementation, the LTR cache changes on a repaint. This is strange
238 // since there is no need to do so. We could events here and
239 // update the cache.
240 // elif the border/insets change, we recalculateInsets.
241 slider.repaint();
246 * Helper class that listens to our swing timer. This class is responsible
247 * for listening to the timer and moving the thumb in the proper direction
248 * every interval.
250 public class ScrollListener implements ActionListener
252 /** Indicates which direction the thumb should scroll. */
253 private transient int direction;
255 /** Indicates whether we should scroll in blocks or in units. */
256 private transient boolean block;
259 * Creates a new ScrollListener object.
261 public ScrollListener()
263 direction = POSITIVE_SCROLL;
264 block = false;
268 * Creates a new ScrollListener object.
270 * @param dir The direction to scroll in.
271 * @param block If movement will be in blocks.
273 public ScrollListener(int dir, boolean block)
275 direction = dir;
276 this.block = block;
280 * Called every time the swing timer reaches its interval. If the thumb
281 * needs to move, then this method will move the thumb one block or unit
282 * in the direction desired. Otherwise, the timer can be stopped.
284 * @param e An {@link ActionEvent}.
286 public void actionPerformed(ActionEvent e)
288 if (! trackListener.shouldScroll(direction))
290 scrollTimer.stop();
291 return;
294 if (block)
295 scrollByBlock(direction);
296 else
297 scrollByUnit(direction);
301 * Sets the direction to scroll in.
303 * @param direction The direction to scroll in.
305 public void setDirection(int direction)
307 this.direction = direction;
311 * Sets whether movement will be in blocks.
313 * @param block If movement will be in blocks.
315 public void setScrollByBlock(boolean block)
317 this.block = block;
322 * Helper class that listens for mouse events.
324 public class TrackListener extends MouseInputAdapter
326 /** The current X position of the mouse. */
327 protected int currentMouseX;
329 /** The current Y position of the mouse. */
330 protected int currentMouseY;
333 * The offset between the current slider value and the cursor's position.
335 protected int offset;
338 * Called when the mouse has been dragged. This should find the mouse's
339 * current position and adjust the value of the {@link JSlider}
340 * accordingly.
342 * @param e A {@link MouseEvent}
344 public void mouseDragged(MouseEvent e)
346 currentMouseX = e.getX();
347 currentMouseY = e.getY();
348 if (slider.getValueIsAdjusting())
350 int value;
351 if (slider.getOrientation() == JSlider.HORIZONTAL)
352 value = valueForXPosition(currentMouseX) - offset;
353 else
354 value = valueForYPosition(currentMouseY) - offset;
356 slider.setValue(value);
361 * Called when the mouse has moved over a component but no buttons have
362 * been pressed yet.
364 * @param e A {@link MouseEvent}
366 public void mouseMoved(MouseEvent e)
368 // Don't care that we're moved unless we're dragging.
372 * Called when the mouse is pressed. When the press occurs on the thumb
373 * itself, the {@link JSlider} should have its value set to where the
374 * mouse was pressed. If the press occurs on the track, then the thumb
375 * should move one block towards the direction of the mouse.
377 * @param e A {@link MouseEvent}
379 public void mousePressed(MouseEvent e)
381 currentMouseX = e.getX();
382 currentMouseY = e.getY();
384 int value;
385 if (slider.getOrientation() == JSlider.HORIZONTAL)
386 value = valueForXPosition(currentMouseX);
387 else
388 value = valueForYPosition(currentMouseY);
390 if (slider.getSnapToTicks())
391 value = findClosestTick(value);
393 // If the thumb is hit, then we don't need to set the timers to move it.
394 if (! thumbRect.contains(e.getPoint()))
396 // The mouse has hit some other part of the slider.
397 // The value moves no matter where in the slider you hit.
398 if (value > slider.getValue())
399 scrollDueToClickInTrack(POSITIVE_SCROLL);
400 else
401 scrollDueToClickInTrack(NEGATIVE_SCROLL);
403 else
405 slider.setValueIsAdjusting(true);
406 offset = value - slider.getValue();
411 * Called when the mouse is released. This should stop the timer that
412 * scrolls the thumb.
414 * @param e A {@link MouseEvent}
416 public void mouseReleased(MouseEvent e)
418 currentMouseX = e.getX();
419 currentMouseY = e.getY();
421 if (slider.getValueIsAdjusting())
423 slider.setValueIsAdjusting(false);
424 if (slider.getSnapToTicks())
425 slider.setValue(findClosestTick(slider.getValue()));
427 if (scrollTimer != null)
428 scrollTimer.stop();
432 * Indicates whether the thumb should scroll in the given direction.
434 * @param direction The direction to check.
436 * @return True if the thumb should move in that direction.
438 public boolean shouldScroll(int direction)
440 int value;
441 if (slider.getOrientation() == JSlider.HORIZONTAL)
442 value = valueForXPosition(currentMouseX);
443 else
444 value = valueForYPosition(currentMouseY);
446 if (direction == POSITIVE_SCROLL)
447 return (value > slider.getValue());
448 else
449 return (value < slider.getValue());
453 /** The preferred height of the thumb. */
454 private transient int thumbHeight;
456 /** The preferred width of the thumb. */
457 private transient int thumbWidth;
459 /** The preferred height of the tick rectangle. */
460 private transient int tickHeight;
462 /** Listener for changes from the model. */
463 protected ChangeListener changeListener;
465 /** Listener for changes to the {@link JSlider}. */
466 protected PropertyChangeListener propertyChangeListener;
468 /** Listener for the scrollTimer. */
469 protected ScrollListener scrollListener;
471 /** Listener for component resizing. */
472 protected ComponentListener componentListener;
474 /** Listener for focus handling. */
475 protected FocusListener focusListener;
477 /** Listener for mouse events. */
478 protected TrackListener trackListener;
480 /** The insets between the FocusRectangle and the ContentRectangle. */
481 protected Insets focusInsets;
483 /** The {@link JSlider}'s insets. */
484 protected Insets insetCache;
486 /** Rectangle describing content bounds. See diagram above. */
487 protected Rectangle contentRect;
489 /** Rectangle describing focus bounds. See diagram above. */
490 protected Rectangle focusRect;
492 /** Rectangle describing the thumb's bounds. See diagram above. */
493 protected Rectangle thumbRect;
495 /** Rectangle describing the tick bounds. See diagram above. */
496 protected Rectangle tickRect;
498 /** Rectangle describing the label bounds. See diagram above. */
499 protected Rectangle labelRect;
501 /** Rectangle describing the track bounds. See diagram above. */
502 protected Rectangle trackRect;
504 /** FIXME: use this somewhere. */
505 public static final int MAX_SCROLL = 2;
507 /** FIXME: use this somewhere. */
508 public static final int MIN_SCROLL = -2;
510 /** A constant describing scrolling towards the minimum. */
511 public static final int NEGATIVE_SCROLL = -1;
513 /** A constant describing scrolling towards the maximum. */
514 public static final int POSITIVE_SCROLL = 1;
516 /** The gap between the edges of the contentRect and trackRect. */
517 protected int trackBuffer;
519 /** Whether this slider is actually drawn left to right. */
520 protected boolean leftToRightCache;
522 /** A timer that periodically moves the thumb. */
523 protected Timer scrollTimer;
525 /** A reference to the {@link JSlider} that this UI was created for. */
526 protected JSlider slider;
528 /** The shadow color. */
529 private transient Color shadowColor;
531 /** The highlight color. */
532 private transient Color highlightColor;
534 /** The focus color. */
535 private transient Color focusColor;
538 * Creates a new Basic look and feel Slider UI.
540 * @param b The {@link JSlider} that this UI was created for.
542 public BasicSliderUI(JSlider b)
544 super();
548 * Gets the shadow color to be used for this slider. The shadow color is the
549 * color used for drawing the top and left edges of the track.
551 * @return The shadow color.
553 protected Color getShadowColor()
555 return shadowColor;
559 * Gets the highlight color to be used for this slider. The highlight color
560 * is the color used for drawing the bottom and right edges of the track.
562 * @return The highlight color.
564 protected Color getHighlightColor()
566 return highlightColor;
570 * Gets the focus color to be used for this slider. The focus color is the
571 * color used for drawing the focus rectangle when the component gains
572 * focus.
574 * @return The focus color.
576 protected Color getFocusColor()
578 return focusColor;
582 * Factory method to create a BasicSliderUI for the given {@link
583 * JComponent}, which should be a {@link JSlider}.
585 * @param b The {@link JComponent} a UI is being created for.
587 * @return A BasicSliderUI for the {@link JComponent}.
589 public static ComponentUI createUI(JComponent b)
591 return new BasicSliderUI((JSlider) b);
595 * Installs and initializes all fields for this UI delegate. Any properties
596 * of the UI that need to be initialized and/or set to defaults will be
597 * done now. It will also install any listeners necessary.
599 * @param c The {@link JComponent} that is having this UI installed.
601 public void installUI(JComponent c)
603 super.installUI(c);
604 if (c instanceof JSlider)
606 slider = (JSlider) c;
608 focusRect = new Rectangle();
609 contentRect = new Rectangle();
610 thumbRect = new Rectangle();
611 trackRect = new Rectangle();
612 tickRect = new Rectangle();
613 labelRect = new Rectangle();
615 insetCache = slider.getInsets();
616 leftToRightCache = ! slider.getInverted();
618 scrollTimer = new Timer(200, null);
619 scrollTimer.setRepeats(true);
621 installDefaults(slider);
622 installListeners(slider);
623 installKeyboardActions(slider);
625 calculateFocusRect();
627 calculateContentRect();
628 calculateThumbSize();
629 calculateTrackBuffer();
630 calculateTrackRect();
631 calculateThumbLocation();
633 calculateTickRect();
634 calculateLabelRect();
639 * Performs the opposite of installUI. Any properties or resources that need
640 * to be cleaned up will be done now. It will also uninstall any listeners
641 * it has. In addition, any properties of this UI will be nulled.
643 * @param c The {@link JComponent} that is having this UI uninstalled.
645 public void uninstallUI(JComponent c)
647 super.uninstallUI(c);
649 uninstallKeyboardActions(slider);
650 uninstallListeners(slider);
652 scrollTimer = null;
654 focusRect = null;
655 contentRect = null;
656 thumbRect = null;
657 trackRect = null;
658 tickRect = null;
659 labelRect = null;
661 focusInsets = null;
665 * Initializes any default properties that this UI has from the defaults for
666 * the Basic look and feel.
668 * @param slider The {@link JSlider} that is having this UI installed.
670 protected void installDefaults(JSlider slider)
672 UIDefaults defaults = UIManager.getLookAndFeelDefaults();
674 slider.setForeground(defaults.getColor("Slider.foreground"));
675 slider.setBackground(defaults.getColor("Slider.background"));
676 shadowColor = defaults.getColor("Slider.shadow");
677 highlightColor = defaults.getColor("Slider.highlight");
678 focusColor = defaults.getColor("Slider.focus");
679 slider.setBorder(defaults.getBorder("Slider.border"));
680 slider.setOpaque(true);
682 thumbHeight = defaults.getInt("Slider.thumbHeight");
683 thumbWidth = defaults.getInt("Slider.thumbWidth");
684 tickHeight = defaults.getInt("Slider.tickHeight");
686 focusInsets = defaults.getInsets("Slider.focusInsets");
690 * Creates a new {@link TrackListener}.
692 * @param slider The {@link JSlider} that this {@link TrackListener} is
693 * created for.
695 * @return A new {@link TrackListener}.
697 protected TrackListener createTrackListener(JSlider slider)
699 return new TrackListener();
703 * Creates a new {@link ChangeListener}.
705 * @param slider The {@link JSlider} that this {@link ChangeListener} is
706 * created for.
708 * @return A new {@link ChangeListener}.
710 protected ChangeListener createChangeListener(JSlider slider)
712 return new ChangeHandler();
716 * Creates a new {@link ComponentListener}.
718 * @param slider The {@link JSlider} that this {@link ComponentListener} is
719 * created for.
721 * @return A new {@link ComponentListener}.
723 protected ComponentListener createComponentListener(JSlider slider)
725 return new ComponentHandler();
729 * Creates a new {@link FocusListener}.
731 * @param slider The {@link JSlider} that this {@link FocusListener} is
732 * created for.
734 * @return A new {@link FocusListener}.
736 protected FocusListener createFocusListener(JSlider slider)
738 return new FocusHandler();
742 * Creates a new {@link ScrollListener}.
744 * @param slider The {@link JSlider} that this {@link ScrollListener} is
745 * created for.
747 * @return A new {@link ScrollListener}.
749 protected ScrollListener createScrollListener(JSlider slider)
751 return new ScrollListener();
755 * Creates a new {@link PropertyChangeListener}.
757 * @param slider The {@link JSlider} that this {@link
758 * PropertyChangeListener} is created for.
760 * @return A new {@link PropertyChangeListener}.
762 protected PropertyChangeListener createPropertyChangeListener(JSlider slider)
764 return new PropertyChangeHandler();
768 * Creates and registers all the listeners for this UI delegate. This
769 * includes creating the ScrollListener and registering it to the timer.
771 * @param slider The {@link JSlider} is having listeners installed.
773 protected void installListeners(JSlider slider)
775 propertyChangeListener = createPropertyChangeListener(slider);
776 componentListener = createComponentListener(slider);
777 trackListener = createTrackListener(slider);
778 focusListener = createFocusListener(slider);
779 changeListener = createChangeListener(slider);
780 scrollListener = createScrollListener(slider);
782 slider.addPropertyChangeListener(propertyChangeListener);
783 slider.addComponentListener(componentListener);
784 slider.addMouseListener(trackListener);
785 slider.addMouseMotionListener(trackListener);
786 slider.addFocusListener(focusListener);
787 slider.getModel().addChangeListener(changeListener);
789 scrollTimer.addActionListener(scrollListener);
793 * Unregisters all the listeners that this UI delegate was using. In
794 * addition, it will also null any listeners that it was using.
796 * @param slider The {@link JSlider} that is having listeners removed.
798 protected void uninstallListeners(JSlider slider)
800 slider.removePropertyChangeListener(propertyChangeListener);
801 slider.removeComponentListener(componentListener);
802 slider.removeMouseListener(trackListener);
803 slider.removeMouseMotionListener(trackListener);
804 slider.removeFocusListener(focusListener);
805 slider.getModel().removeChangeListener(changeListener);
807 scrollTimer.removeActionListener(scrollListener);
809 propertyChangeListener = null;
810 componentListener = null;
811 trackListener = null;
812 focusListener = null;
813 changeListener = null;
814 scrollListener = null;
818 * Installs any keyboard actions. The list of keys that need to be bound are
819 * listed in Basic look and feel's defaults.
821 * @param slider The {@link JSlider} that is having keyboard actions
822 * installed.
824 protected void installKeyboardActions(JSlider slider)
826 // FIXME: implement.
830 * Uninstalls any keyboard actions. The list of keys used are listed in
831 * Basic look and feel's defaults.
833 * @param slider The {@link JSlider} that is having keyboard actions
834 * uninstalled.
836 protected void uninstallKeyboardActions(JSlider slider)
838 // FIXME: implement.
841 /* XXX: This is all after experimentation with SUN's implementation.
843 PreferredHorizontalSize seems to be 200x21.
844 PreferredVerticalSize seems to be 21x200.
846 MinimumHorizontalSize seems to be 36x21.
847 MinimumVerticalSize seems to be 21x36.
849 PreferredSize seems to be 200x63. Or Components.getBounds?
851 MinimumSize seems to be 36x63.
853 MaximumSize seems to be 32767x63.
857 * This method returns the preferred size when the slider is horizontally
858 * oriented.
860 * @return The dimensions of the preferred horizontal size.
862 public Dimension getPreferredHorizontalSize()
864 Insets insets = slider.getInsets();
866 // The width should cover all the labels (which are usually the
867 // deciding factor of the width)
868 int width = getWidthOfWidestLabel() * (slider.getLabelTable() == null ? 0
869 : slider.getLabelTable()
870 .size());
872 // If there are not enough labels.
873 // This number is pretty much arbitrary, but it looks nice.
874 if (width < 200)
875 width = 200;
877 // We can only draw inside of the focusRectangle, so we have to
878 // pad it with insets.
879 width += insets.left + insets.right + focusInsets.left + focusInsets.right;
881 // Height is determined by the thumb, the ticks and the labels.
882 int height = thumbHeight;
884 if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0
885 || slider.getMinorTickSpacing() > 0)
886 height += tickHeight;
888 if (slider.getPaintLabels())
889 height += getHeightOfTallestLabel();
891 height += insets.top + insets.bottom + focusInsets.top
892 + focusInsets.bottom;
894 return new Dimension(width, height);
898 * This method returns the preferred size when the slider is vertically
899 * oriented.
901 * @return The dimensions of the preferred vertical size.
903 public Dimension getPreferredVerticalSize()
905 Insets insets = slider.getInsets();
907 int height = getHeightOfTallestLabel() * (slider.getLabelTable() == null
908 ? 0 : slider.getLabelTable()
909 .size());
911 if (height < 200)
912 height = 200;
914 height += insets.top + insets.bottom + focusInsets.top
915 + focusInsets.bottom;
917 int width = thumbHeight;
919 if (slider.getPaintTicks() && slider.getMajorTickSpacing() > 0
920 || slider.getMinorTickSpacing() > 0)
921 width += tickHeight;
923 if (slider.getPaintLabels())
924 width += getWidthOfWidestLabel();
926 width += insets.left + insets.right + focusInsets.left + focusInsets.right;
928 return new Dimension(width, height);
932 * This method returns the minimum size when the slider is horizontally
933 * oriented.
935 * @return The dimensions of the minimum horizontal size.
937 public Dimension getMinimumHorizontalSize()
939 return getPreferredHorizontalSize();
943 * This method returns the minimum size of the slider when it is vertically
944 * oriented.
946 * @return The dimensions of the minimum vertical size.
948 public Dimension getMinimumVerticalSize()
950 return getPreferredVerticalSize();
954 * This method returns the preferred size of the component. If it returns
955 * null, then it is up to the Layout Manager to give the {@link JComponent}
956 * a size.
958 * @param c The {@link JComponent} to find the preferred size for.
960 * @return The dimensions of the preferred size.
962 public Dimension getPreferredSize(JComponent c)
964 if (slider.getOrientation() == JSlider.HORIZONTAL)
965 return getPreferredHorizontalSize();
966 else
967 return getPreferredVerticalSize();
971 * This method returns the minimum size for this {@link JSlider} for this
972 * look and feel. If it returns null, then it is up to the Layout Manager
973 * to give the {@link JComponent} a size.
975 * @param c The {@link JComponent} to find the minimum size for.
977 * @return The dimensions of the minimum size.
979 public Dimension getMinimumSize(JComponent c)
981 if (slider.getOrientation() == JSlider.HORIZONTAL)
982 return getPreferredHorizontalSize();
983 else
984 return getPreferredVerticalSize();
988 * This method returns the maximum size for this {@link JSlider} for this
989 * look and feel. If it returns null, then it is up to the Layout Manager
990 * to give the {@link JComponent} a size.
992 * @param c The {@link JComponent} to find a maximum size for.
994 * @return The dimensions of the maximum size.
996 public Dimension getMaximumSize(JComponent c)
998 if (slider.getOrientation() == JSlider.HORIZONTAL)
999 return getPreferredHorizontalSize();
1000 else
1001 return getPreferredVerticalSize();
1005 * This method calculates all the sizes of the rectangles by delegating to
1006 * the helper methods calculateXXXRect.
1008 protected void calculateGeometry()
1010 calculateFocusRect();
1011 calculateContentRect();
1012 calculateThumbSize();
1013 calculateTrackBuffer();
1014 calculateTrackRect();
1015 calculateTickRect();
1016 calculateLabelRect();
1017 calculateThumbLocation();
1021 * This method calculates the size and position of the focusRect. This
1022 * method does not need to be called if the orientation changes.
1024 protected void calculateFocusRect()
1026 insetCache = slider.getInsets();
1027 focusRect = SwingUtilities.calculateInnerArea(slider, focusRect);
1029 if (focusRect.width < 0)
1030 focusRect.width = 0;
1031 if (focusRect.height < 0)
1032 focusRect.height = 0;
1036 * This method calculates the size but not the position of the thumbRect. It
1037 * must take into account the orientation of the slider.
1039 protected void calculateThumbSize()
1041 if (slider.getOrientation() == JSlider.HORIZONTAL)
1043 if (thumbWidth > contentRect.width)
1044 thumbRect.width = contentRect.width / 4;
1045 else
1046 thumbRect.width = thumbWidth;
1047 if (thumbHeight > contentRect.height)
1048 thumbRect.height = contentRect.height;
1049 else
1050 thumbRect.height = thumbHeight;
1052 else
1054 // The thumb gets flipped when inverted, so thumbWidth
1055 // actually is the height and vice versa.
1056 if (thumbWidth > contentRect.height)
1057 thumbRect.height = contentRect.height / 4;
1058 else
1059 thumbRect.height = thumbWidth;
1060 if (thumbHeight > contentRect.width)
1061 thumbRect.width = contentRect.width;
1062 else
1063 thumbRect.width = thumbHeight;
1068 * This method calculates the size and position of the contentRect. This
1069 * method does not need to be called if the orientation changes.
1071 protected void calculateContentRect()
1073 contentRect.x = focusRect.x + focusInsets.left;
1074 contentRect.y = focusRect.y + focusInsets.top;
1075 contentRect.width = focusRect.width - focusInsets.left - focusInsets.right;
1076 contentRect.height = focusRect.height - focusInsets.top
1077 - focusInsets.bottom;
1079 if (contentRect.width < 0)
1080 contentRect.width = 0;
1081 if (contentRect.height < 0)
1082 contentRect.height = 0;
1086 * Calculates the position of the thumbRect based on the current value of
1087 * the slider. It must take into account the orientation of the slider.
1089 protected void calculateThumbLocation()
1091 int value = slider.getValue();
1093 if (slider.getOrientation() == JSlider.HORIZONTAL)
1095 thumbRect.x = xPositionForValue(value) - thumbRect.width / 2;
1096 thumbRect.y = contentRect.y;
1098 else
1100 thumbRect.x = contentRect.x;
1101 thumbRect.y = yPositionForValue(value) - thumbRect.height / 2;
1106 * Calculates the gap size between the left edge of the contentRect and the
1107 * left edge of the trackRect.
1109 protected void calculateTrackBuffer()
1111 if (slider.getOrientation() == JSlider.HORIZONTAL)
1112 trackBuffer = thumbRect.width;
1113 else
1114 trackBuffer = thumbRect.height;
1118 * This method returns the size of the thumbRect.
1120 * @return The dimensions of the thumb.
1122 protected Dimension getThumbSize()
1124 // This is really just the bounds box for the thumb.
1125 // The thumb will actually be pointed (like a rectangle + triangle at bottom)
1126 return thumbRect.getSize();
1130 * Calculates the size and position of the trackRect. It must take into
1131 * account the orientation of the slider.
1133 protected void calculateTrackRect()
1135 if (slider.getOrientation() == JSlider.HORIZONTAL)
1137 trackRect.x = contentRect.x + trackBuffer;
1138 trackRect.y = contentRect.y;
1139 trackRect.width = contentRect.width - 2 * trackBuffer;
1140 trackRect.height = thumbRect.height;
1142 else
1144 trackRect.x = contentRect.x;
1145 trackRect.y = contentRect.y + trackBuffer;
1146 trackRect.width = thumbRect.width;
1147 trackRect.height = contentRect.height - 2 * trackBuffer;
1152 * This method returns the height of the tick area box if the slider is
1153 * horizontal and the width of the tick area box is the slider is vertical.
1154 * It not necessarily how long the ticks will be. If a gap between the edge
1155 * of tick box and the actual tick is desired, then that will need to be
1156 * handled in the tick painting methods.
1158 * @return The height (or width if the slider is vertical) of the tick
1159 * rectangle.
1161 protected int getTickLength()
1163 return tickHeight;
1167 * This method calculates the size and position of the tickRect. It must
1168 * take into account the orientation of the slider.
1170 protected void calculateTickRect()
1172 if (slider.getOrientation() == JSlider.HORIZONTAL)
1174 tickRect.x = trackRect.x;
1175 tickRect.y = trackRect.y + trackRect.height;
1176 tickRect.width = trackRect.width;
1177 tickRect.height = getTickLength();
1179 if (tickRect.y + tickRect.height > contentRect.y + contentRect.height)
1180 tickRect.height = contentRect.y + contentRect.height - tickRect.y;
1182 else
1184 tickRect.x = trackRect.x + trackRect.width;
1185 tickRect.y = trackRect.y;
1186 tickRect.width = getTickLength();
1187 tickRect.height = trackRect.height;
1189 if (tickRect.x + tickRect.width > contentRect.x + contentRect.width)
1190 tickRect.width = contentRect.x + contentRect.width - tickRect.x;
1195 * This method calculates the size and position of the labelRect. It must
1196 * take into account the orientation of the slider.
1198 protected void calculateLabelRect()
1200 if (slider.getOrientation() == JSlider.HORIZONTAL)
1202 labelRect.x = contentRect.x;
1203 labelRect.y = tickRect.y + tickRect.height;
1204 labelRect.width = contentRect.width;
1205 labelRect.height = contentRect.height - labelRect.y;
1207 else
1209 labelRect.x = tickRect.x + tickRect.width;
1210 labelRect.y = contentRect.y;
1211 labelRect.width = contentRect.width - labelRect.x;
1212 labelRect.height = contentRect.height;
1217 * This method returns the width of the widest label in the slider's label
1218 * table.
1220 * @return The width of the widest label or 0 if no label table exists.
1222 protected int getWidthOfWidestLabel()
1224 int widest = 0;
1225 Component label;
1227 if (slider.getLabelTable() == null)
1228 return 0;
1230 Dimension pref;
1231 for (Enumeration list = slider.getLabelTable().elements();
1232 list.hasMoreElements();)
1234 Object comp = list.nextElement();
1235 if (! (comp instanceof Component))
1236 continue;
1237 label = (Component) comp;
1238 pref = label.getPreferredSize();
1239 if (pref != null && pref.width > widest)
1240 widest = pref.width;
1242 return widest;
1246 * This method returns the height of the tallest label in the slider's label
1247 * table.
1249 * @return The height of the tallest label or 0 if no label table exists.
1251 protected int getHeightOfTallestLabel()
1253 int tallest = 0;
1254 Component label;
1256 if (slider.getLabelTable() == null)
1257 return 0;
1258 Dimension pref;
1259 for (Enumeration list = slider.getLabelTable().elements();
1260 list.hasMoreElements();)
1262 Object comp = list.nextElement();
1263 if (! (comp instanceof Component))
1264 continue;
1265 label = (Component) comp;
1266 pref = label.getPreferredSize();
1267 if (pref != null && pref.height > tallest)
1268 tallest = pref.height;
1270 return tallest;
1274 * This method returns the width of the label whose key has the highest
1275 * value.
1277 * @return The width of the high value label or 0 if no label table exists.
1279 protected int getWidthOfHighValueLabel()
1281 Component highValueLabel = getHighestValueLabel();
1282 if (highValueLabel != null)
1283 return highValueLabel.getWidth();
1284 else
1285 return 0;
1289 * This method returns the width of the label whose key has the lowest
1290 * value.
1292 * @return The width of the low value label or 0 if no label table exists.
1294 protected int getWidthOfLowValueLabel()
1296 Component lowValueLabel = getLowestValueLabel();
1297 if (lowValueLabel != null)
1298 return lowValueLabel.getWidth();
1299 else
1300 return 0;
1304 * This method returns the height of the label whose key has the highest
1305 * value.
1307 * @return The height of the high value label or 0 if no label table exists.
1309 protected int getHeightOfHighValueLabel()
1311 Component highValueLabel = getHighestValueLabel();
1312 if (highValueLabel != null)
1313 return highValueLabel.getHeight();
1314 else
1315 return 0;
1319 * This method returns the height of the label whose key has the lowest
1320 * value.
1322 * @return The height of the low value label or 0 if no label table exists.
1324 protected int getHeightOfLowValueLabel()
1326 Component lowValueLabel = getLowestValueLabel();
1327 if (lowValueLabel != null)
1328 return lowValueLabel.getHeight();
1329 else
1330 return 0;
1334 * This method returns whether the slider is to be drawn inverted.
1336 * @return True is the slider is to be drawn inverted.
1338 protected boolean drawInverted()
1340 return ! (slider.getInverted() ^ leftToRightCache);
1344 * This method returns the label whose key has the lowest value.
1346 * @return The low value label or null if no label table exists.
1348 protected Component getLowestValueLabel()
1350 Integer key = new Integer(Integer.MAX_VALUE);
1351 Integer tmpKey;
1352 Dictionary labelTable = slider.getLabelTable();
1354 if (labelTable == null)
1355 return null;
1357 for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
1359 Object value = list.nextElement();
1360 if (! (value instanceof Integer))
1361 continue;
1362 tmpKey = (Integer) value;
1363 if (tmpKey.intValue() < key.intValue())
1364 key = tmpKey;
1366 Object comp = labelTable.get(key);
1367 if (! (comp instanceof Component))
1368 return null;
1369 return (Component) comp;
1373 * This method returns the label whose key has the highest value.
1375 * @return The high value label or null if no label table exists.
1377 protected Component getHighestValueLabel()
1379 Integer key = new Integer(Integer.MIN_VALUE);
1380 Integer tmpKey;
1381 Dictionary labelTable = slider.getLabelTable();
1383 if (labelTable == null)
1384 return null;
1386 for (Enumeration list = labelTable.keys(); list.hasMoreElements();)
1388 Object value = list.nextElement();
1389 if (! (value instanceof Integer))
1390 continue;
1391 tmpKey = (Integer) value;
1392 if (tmpKey.intValue() > key.intValue())
1393 key = tmpKey;
1395 Object comp = labelTable.get(key);
1396 if (! (comp instanceof Component))
1397 return null;
1398 return (Component) comp;
1402 * This method is used to paint the {@link JSlider}. It delegates all its
1403 * duties to the various paint methods like paintTicks(), paintTrack(),
1404 * paintThumb(), etc.
1406 * @param g The {@link Graphics} object to paint with.
1407 * @param c The {@link JComponent} that is being painted.
1409 public void paint(Graphics g, JComponent c)
1411 // FIXME: Move this to propertyChangeEvent handler, when we get those.
1412 leftToRightCache = slider.getComponentOrientation() != ComponentOrientation.RIGHT_TO_LEFT;
1413 // FIXME: This next line is only here because the above line is here.
1414 calculateThumbLocation();
1416 if (slider.getPaintTrack())
1417 paintTrack(g);
1418 if (slider.getPaintTicks())
1419 paintTicks(g);
1420 if (slider.getPaintLabels())
1421 paintLabels(g);
1423 //FIXME: Paint focus.
1424 paintThumb(g);
1428 * This method recalculates any rectangles that need to be recalculated
1429 * after the insets of the component have changed.
1431 protected void recalculateIfInsetsChanged()
1433 // Examining a test program shows that either Sun calls private
1434 // methods that we don't know about, or these don't do anything.
1435 calculateFocusRect();
1437 calculateContentRect();
1438 calculateThumbSize();
1439 calculateTrackBuffer();
1440 calculateTrackRect();
1441 calculateThumbLocation();
1443 calculateTickRect();
1444 calculateLabelRect();
1448 * This method recalculates any rectangles that need to be recalculated
1449 * after the orientation of the slider changes.
1451 protected void recalculateIfOrientationChanged()
1453 // Examining a test program shows that either Sun calls private
1454 // methods that we don't know about, or these don't do anything.
1455 calculateThumbSize();
1456 calculateTrackBuffer();
1457 calculateTrackRect();
1458 calculateThumbLocation();
1460 calculateTickRect();
1461 calculateLabelRect();
1465 * This method is called during a repaint if the slider has focus. It draws
1466 * an outline of the focusRect using the color returned by
1467 * getFocusColor().
1469 * @param g The {@link Graphics} object to draw with.
1471 public void paintFocus(Graphics g)
1473 Color saved_color = g.getColor();
1475 g.setColor(getFocusColor());
1477 g.drawRect(focusRect.x, focusRect.y, focusRect.width, focusRect.height);
1479 g.setColor(saved_color);
1483 * <p>
1484 * This method is called during a repaint if the track is to be drawn. It
1485 * draws a 3D rectangle to represent the track. The track is not the size
1486 * of the trackRect. The top and left edges of the track should be outlined
1487 * with the shadow color. The bottom and right edges should be outlined
1488 * with the highlight color.
1489 * </p>
1490 * <pre>
1491 * a---d
1492 * | |
1493 * | | a------------------------d
1494 * | | | |
1495 * | | b------------------------c
1496 * | |
1497 * | |
1498 * b---c
1499 * </pre>
1501 * <p>
1502 * The b-a-d path needs to be drawn with the shadow color and the b-c-d path
1503 * needs to be drawn with the highlight color.
1504 * </p>
1506 * @param g The {@link Graphics} object to draw with.
1508 public void paintTrack(Graphics g)
1510 Color saved_color = g.getColor();
1511 int width;
1512 int height;
1514 Point a = new Point(trackRect.x, trackRect.y);
1515 Point b = new Point(a);
1516 Point c = new Point(a);
1517 Point d = new Point(a);
1519 Polygon high;
1520 Polygon shadow;
1522 if (slider.getOrientation() == JSlider.HORIZONTAL)
1524 width = trackRect.width;
1525 height = (thumbRect.height / 4 == 0) ? 1 : thumbRect.height / 4;
1527 a.translate(0, (trackRect.height / 2) - (height / 2));
1528 b.translate(0, (trackRect.height / 2) + (height / 2));
1529 c.translate(trackRect.width, (trackRect.height / 2) + (height / 2));
1530 d.translate(trackRect.width, (trackRect.height / 2) - (height / 2));
1532 else
1534 width = (thumbRect.width / 4 == 0) ? 1 : thumbRect.width / 4;
1535 height = trackRect.height;
1537 a.translate((trackRect.width / 2) - (width / 2), 0);
1538 b.translate((trackRect.width / 2) - (width / 2), trackRect.height);
1539 c.translate((trackRect.width / 2) + (width / 2), trackRect.height);
1540 d.translate((trackRect.width / 2) + (width / 2), 0);
1542 g.setColor(Color.GRAY);
1543 g.fillRect(a.x, a.y, width, height);
1545 g.setColor(getHighlightColor());
1546 g.drawLine(b.x, b.y, c.x, c.y);
1547 g.drawLine(c.x, c.y, d.x, d.y);
1549 g.setColor(getShadowColor());
1550 g.drawLine(b.x, b.y, a.x, a.y);
1551 g.drawLine(a.x, a.y, d.x, d.y);
1553 g.setColor(saved_color);
1557 * This method is called during a repaint if the ticks are to be drawn. This
1558 * method must still verify that the majorTickSpacing and minorTickSpacing
1559 * are greater than zero before drawing the ticks.
1561 * @param g The {@link Graphics} object to draw with.
1563 public void paintTicks(Graphics g)
1565 int max = slider.getMaximum();
1566 int min = slider.getMinimum();
1567 int majorSpace = slider.getMajorTickSpacing();
1568 int minorSpace = slider.getMinorTickSpacing();
1570 if (majorSpace > 0)
1572 if (slider.getOrientation() == JSlider.HORIZONTAL)
1574 double loc = tickRect.x;
1575 double increment = (max == min) ? 0
1576 : majorSpace * (double) tickRect.width / (max
1577 - min);
1578 if (drawInverted())
1580 loc += tickRect.width;
1581 increment *= -1;
1583 for (int i = min; i <= max; i += majorSpace)
1585 paintMajorTickForHorizSlider(g, tickRect, (int) loc);
1586 loc += increment;
1589 else
1591 double loc = tickRect.height + tickRect.y;
1592 double increment = (max == min) ? 0
1593 : -majorSpace * (double) tickRect.height / (max
1594 - min);
1595 if (drawInverted())
1597 loc = tickRect.y;
1598 increment *= -1;
1600 for (int i = min; i <= max; i += majorSpace)
1602 paintMajorTickForVertSlider(g, tickRect, (int) loc);
1603 loc += increment;
1607 if (minorSpace > 0)
1609 if (slider.getOrientation() == JSlider.HORIZONTAL)
1611 double loc = tickRect.x;
1612 double increment = (max == min) ? 0
1613 : minorSpace * (double) tickRect.width / (max
1614 - min);
1615 if (drawInverted())
1617 loc += tickRect.width;
1618 increment *= -1;
1620 for (int i = min; i <= max; i += minorSpace)
1622 paintMinorTickForHorizSlider(g, tickRect, (int) loc);
1623 loc += increment;
1626 else
1628 double loc = tickRect.height + tickRect.y;
1629 double increment = (max == min) ? 0
1630 : -minorSpace * (double) tickRect.height / (max
1631 - min);
1632 if (drawInverted())
1634 loc = tickRect.y;
1635 increment *= -1;
1637 for (int i = min; i <= max; i += minorSpace)
1639 paintMinorTickForVertSlider(g, tickRect, (int) loc);
1640 loc += increment;
1646 /* Minor ticks start at 1/4 of the height (or width) of the tickRect and extend
1647 to 1/2 of the tickRect.
1649 Major ticks start at 1/4 of the height and extend to 3/4.
1653 * This method paints a minor tick for a horizontal slider at the given x
1654 * value. x represents the x coordinate to paint at.
1656 * @param g The {@link Graphics} object to draw with.
1657 * @param tickBounds The tickRect rectangle.
1658 * @param x The x coordinate to draw the tick at.
1660 protected void paintMinorTickForHorizSlider(Graphics g,
1661 Rectangle tickBounds, int x)
1663 int y = tickRect.y + tickRect.height / 4;
1664 Color saved = g.getColor();
1665 g.setColor(Color.BLACK);
1667 g.drawLine(x, y, x, y + tickRect.height / 4);
1668 g.setColor(saved);
1672 * This method paints a major tick for a horizontal slider at the given x
1673 * value. x represents the x coordinate to paint at.
1675 * @param g The {@link Graphics} object to draw with.
1676 * @param tickBounds The tickRect rectangle.
1677 * @param x The x coordinate to draw the tick at.
1679 protected void paintMajorTickForHorizSlider(Graphics g,
1680 Rectangle tickBounds, int x)
1682 int y = tickRect.y + tickRect.height / 4;
1683 Color saved = g.getColor();
1684 g.setColor(Color.BLACK);
1686 g.drawLine(x, y, x, y + tickRect.height / 2);
1687 g.setColor(saved);
1691 * This method paints a minor tick for a vertical slider at the given y
1692 * value. y represents the y coordinate to paint at.
1694 * @param g The {@link Graphics} object to draw with.
1695 * @param tickBounds The tickRect rectangle.
1696 * @param y The y coordinate to draw the tick at.
1698 protected void paintMinorTickForVertSlider(Graphics g, Rectangle tickBounds,
1699 int y)
1701 int x = tickRect.x + tickRect.width / 4;
1702 Color saved = g.getColor();
1703 g.setColor(Color.BLACK);
1705 g.drawLine(x, y, x + tickRect.width / 4, y);
1706 g.setColor(saved);
1710 * This method paints a major tick for a vertical slider at the given y
1711 * value. y represents the y coordinate to paint at.
1713 * @param g The {@link Graphics} object to draw with.
1714 * @param tickBounds The tickRect rectangle.
1715 * @param y The y coordinate to draw the tick at.
1717 protected void paintMajorTickForVertSlider(Graphics g, Rectangle tickBounds,
1718 int y)
1720 int x = tickRect.x + tickRect.width / 4;
1721 Color saved = g.getColor();
1722 g.setColor(Color.BLACK);
1724 g.drawLine(x, y, x + tickRect.width / 2, y);
1725 g.setColor(saved);
1729 * This method paints all the labels from the slider's label table. This
1730 * method must make sure that the label table is not null before painting
1731 * the labels. Each entry in the label table is a (integer, component)
1732 * pair. Every label is painted at the value of the integer.
1734 * @param g The {@link Graphics} object to draw with.
1736 public void paintLabels(Graphics g)
1738 if (slider.getLabelTable() != null)
1740 Dictionary table = slider.getLabelTable();
1741 Integer tmpKey;
1742 Object key;
1743 Object element;
1744 Component label;
1745 if (slider.getOrientation() == JSlider.HORIZONTAL)
1747 for (Enumeration list = table.keys(); list.hasMoreElements();)
1749 key = list.nextElement();
1750 if (! (key instanceof Integer))
1751 continue;
1752 tmpKey = (Integer) key;
1753 element = table.get(tmpKey);
1754 // We won't paint them if they're not
1755 // JLabels so continue anyway
1756 if (! (element instanceof JLabel))
1757 continue;
1758 label = (Component) element;
1759 paintHorizontalLabel(g, tmpKey.intValue(), label);
1762 else
1764 for (Enumeration list = table.keys(); list.hasMoreElements();)
1766 key = list.nextElement();
1767 if (! (key instanceof Integer))
1768 continue;
1769 tmpKey = (Integer) key;
1770 element = table.get(tmpKey);
1771 // We won't paint them if they're not
1772 // JLabels so continue anyway
1773 if (! (element instanceof JLabel))
1774 continue;
1775 label = (Component) element;
1776 paintVerticalLabel(g, tmpKey.intValue(), label);
1783 * This method paints the label on the horizontal slider at the value
1784 * specified. The value is not a coordinate. It is a value within the range
1785 * of the slider. If the value is not within the range of the slider, this
1786 * method will do nothing. This method should not paint outside the
1787 * boundaries of the labelRect.
1789 * @param g The {@link Graphics} object to draw with.
1790 * @param value The value to paint at.
1791 * @param label The label to paint.
1793 protected void paintHorizontalLabel(Graphics g, int value, Component label)
1795 // This relies on clipping working properly or we'll end up
1796 // painting all over the place. If our preferred size is ignored, then
1797 // the labels may not fit inside the slider's bounds. Rather than mucking
1798 // with font sizes and possible icon sizes, we'll set the bounds for
1799 // the label and let it get clipped.
1800 Dimension dim = label.getPreferredSize();
1801 int w = (int) dim.getWidth();
1802 int h = (int) dim.getHeight();
1804 int max = slider.getMaximum();
1805 int min = slider.getMinimum();
1807 if (value > max || value < min)
1808 return;
1810 // value
1811 // |
1812 // ------------
1813 // | |
1814 // | |
1815 // | |
1816 // The label must move w/2 to the right to fit directly under the value.
1817 int xpos = xPositionForValue(value) - w / 2;
1818 int ypos = labelRect.y;
1820 // We want to center the label around the xPositionForValue
1821 // So we use xpos - w / 2. However, if value is min and the label
1822 // is large, we run the risk of going out of bounds. So we bring it back
1823 // to 0 if it becomes negative.
1824 if (xpos < 0)
1825 xpos = 0;
1827 // If the label + starting x position is greater than
1828 // the x space in the label rectangle, we reset it to the largest
1829 // amount possible in the rectangle. This means ugliness.
1830 if (xpos + w > labelRect.x + labelRect.width)
1831 w = labelRect.x + labelRect.width - xpos;
1833 // If the label is too tall. We reset it to the height of the label
1834 // rectangle.
1835 if (h > labelRect.height)
1836 h = labelRect.height;
1838 label.setBounds(xpos, ypos, w, h);
1839 javax.swing.SwingUtilities.paintComponent(g, label, null, label.getBounds());
1843 * This method paints the label on the vertical slider at the value
1844 * specified. The value is not a coordinate. It is a value within the range
1845 * of the slider. If the value is not within the range of the slider, this
1846 * method will do nothing. This method should not paint outside the
1847 * boundaries of the labelRect.
1849 * @param g The {@link Graphics} object to draw with.
1850 * @param value The value to paint at.
1851 * @param label The label to paint.
1853 protected void paintVerticalLabel(Graphics g, int value, Component label)
1855 Dimension dim = label.getPreferredSize();
1856 int w = (int) dim.getWidth();
1857 int h = (int) dim.getHeight();
1859 int max = slider.getMaximum();
1860 int min = slider.getMinimum();
1862 if (value > max || value < min)
1863 return;
1865 int xpos = labelRect.x;
1866 int ypos = yPositionForValue(value) - h / 2;
1868 if (ypos < 0)
1869 ypos = 0;
1871 if (ypos + h > labelRect.y + labelRect.height)
1872 h = labelRect.y + labelRect.height - ypos;
1874 if (w > labelRect.width)
1875 w = labelRect.width;
1877 label.setBounds(xpos, ypos, w, h);
1878 javax.swing.SwingUtilities.paintComponent(g, label, null, label.getBounds());
1882 * <p>
1883 * This method paints a thumb. There are two types of thumb:
1884 * </p>
1885 * <pre>
1886 * Vertical Horizontal
1887 * a---b a-----b
1888 * | | | \
1889 * e c | c
1890 * \ / | /
1891 * d e-----d
1892 * </pre>
1894 * <p>
1895 * In the case of vertical thumbs, we highlight the path b-a-e-d and shadow
1896 * the path b-c-d. In the case of horizontal thumbs, we highlight the path
1897 * c-b-a-e and shadow the path c-d-e. In both cases we fill the path
1898 * a-b-c-d-e before shadows and highlights are drawn.
1899 * </p>
1901 * @param g The graphics object to paint with
1903 public void paintThumb(Graphics g)
1905 Color saved_color = g.getColor();
1907 Polygon thumb = new Polygon();
1909 Point a = new Point(thumbRect.x, thumbRect.y);
1910 Point b = new Point(a);
1911 Point c = new Point(a);
1912 Point d = new Point(a);
1913 Point e = new Point(a);
1915 Polygon bright;
1916 Polygon dark;
1917 Polygon all;
1919 // This will be in X-dimension if the slider is inverted and y if it isn't.
1920 int turnPoint;
1922 if (slider.getOrientation() == JSlider.HORIZONTAL)
1924 turnPoint = thumbRect.height * 3 / 4;
1926 b.translate(thumbRect.width, 0);
1927 c.translate(thumbRect.width, turnPoint);
1928 d.translate(thumbRect.width / 2, thumbRect.height);
1929 e.translate(0, turnPoint);
1931 bright = new Polygon(new int[] { b.x, a.x, e.x, d.x },
1932 new int[] { b.y, a.y, e.y, d.y }, 4);
1934 dark = new Polygon(new int[] { b.x, c.x, d.x },
1935 new int[] { b.y, c.y, d.y }, 3);
1936 all = new Polygon(new int[] { a.x + 1, b.x, c.x, d.x, e.x + 1 },
1937 new int[] { a.y + 1, b.y + 1, c.y, d.y + 1, e.y }, 5);
1939 else
1941 turnPoint = thumbRect.width * 3 / 4;
1943 b.translate(turnPoint, 0);
1944 c.translate(thumbRect.width, thumbRect.height / 2);
1945 d.translate(turnPoint, thumbRect.height);
1946 e.translate(0, thumbRect.height);
1948 bright = new Polygon(new int[] { c.x, b.x, a.x, e.x },
1949 new int[] { c.y, b.y, a.y, e.y }, 4);
1951 dark = new Polygon(new int[] { c.x, d.x, e.x + 1 },
1952 new int[] { c.y, d.y, e.y }, 3);
1954 all = new Polygon(new int[] { a.x + 1, b.x, c.x - 1, d.x, e.x + 1 },
1955 new int[] { a.y + 1, b.y + 1, c.y, d.y, e.y }, 5);
1958 g.setColor(Color.WHITE);
1959 g.drawPolyline(bright.xpoints, bright.ypoints, bright.npoints);
1961 g.setColor(Color.BLACK);
1962 g.drawPolyline(dark.xpoints, dark.ypoints, dark.npoints);
1964 g.setColor(Color.GRAY);
1965 g.fillPolygon(all);
1967 g.setColor(saved_color);
1971 * This method sets the position of the thumbRect.
1973 * @param x The new x position.
1974 * @param y The new y position.
1976 public void setThumbLocation(int x, int y)
1978 thumbRect.x = x;
1979 thumbRect.y = y;
1983 * This method is used to move the thumb one block in the direction
1984 * specified. If the slider snaps to ticks, this method is responsible for
1985 * snapping it to a tick after the thumb has been moved.
1987 * @param direction The direction to move in.
1989 public void scrollByBlock(int direction)
1991 // The direction is -1 for backwards and 1 for forwards.
1992 int unit = direction * (slider.getMaximum() - slider.getMinimum()) / 10;
1994 int moveTo = slider.getValue() + unit;
1996 if (slider.getSnapToTicks())
1997 moveTo = findClosestTick(moveTo);
1999 slider.setValue(moveTo);
2003 * This method is used to move the thumb one unit in the direction
2004 * specified. If the slider snaps to ticks, this method is responsible for
2005 * snapping it to a tick after the thumb has been moved.
2007 * @param direction The direction to move in.
2009 public void scrollByUnit(int direction)
2011 // The direction is -1 for backwards and 1 for forwards.
2012 int moveTo = slider.getValue() + direction;
2014 if (slider.getSnapToTicks())
2015 moveTo = findClosestTick(moveTo);
2017 slider.setValue(moveTo);
2021 * This method is called when there has been a click in the track and the
2022 * thumb needs to be scrolled on regular intervals. This method is only
2023 * responsible for starting the timer and not for stopping it.
2025 * @param dir The direction to move in.
2027 protected void scrollDueToClickInTrack(int dir)
2029 scrollTimer.stop();
2031 scrollListener.setDirection(dir);
2032 scrollListener.setScrollByBlock(true);
2034 scrollTimer.start();
2038 * This method returns the X coordinate for the value passed in.
2040 * @param value The value to calculate an x coordinate for.
2042 * @return The x coordinate for the value.
2044 protected int xPositionForValue(int value)
2046 int min = slider.getMinimum();
2047 int max = slider.getMaximum();
2048 int extent = slider.getExtent();
2049 int len = trackRect.width;
2051 int xPos = (max == min) ? 0 : (value - min) * len / (max - min);
2053 if (! drawInverted())
2054 xPos += trackRect.x;
2055 else
2057 xPos = trackRect.width - xPos;
2058 xPos += trackRect.x;
2060 return xPos;
2064 * This method returns the y coordinate for the value passed in.
2066 * @param value The value to calculate a y coordinate for.
2068 * @return The y coordinate for the value.
2070 protected int yPositionForValue(int value)
2072 int min = slider.getMinimum();
2073 int max = slider.getMaximum();
2074 int extent = slider.getExtent();
2075 int len = trackRect.height;
2077 int yPos = (max == min) ? 0 : (value - min) * len / (max - min);
2079 if (! drawInverted())
2081 yPos = trackRect.height - yPos;
2082 yPos += trackRect.y;
2084 else
2085 yPos += trackRect.y;
2086 return yPos;
2090 * This method returns the value in the slider's range given the y
2091 * coordinate. If the value is out of range, it will return the closest
2092 * legal value.
2094 * @param yPos The y coordinate to calculate a value for.
2096 * @return The value for the y coordinate.
2098 public int valueForYPosition(int yPos)
2100 int min = slider.getMinimum();
2101 int max = slider.getMaximum();
2102 int len = trackRect.height;
2104 int value;
2106 // If the length is 0, you shouldn't be able to even see where the slider is.
2107 // This really shouldn't ever happen, but just in case, we'll return the middle.
2108 if (len == 0)
2109 return ((max - min) / 2);
2111 if (! drawInverted())
2112 value = ((len - (yPos - trackRect.y)) * (max - min) / len + min);
2113 else
2114 value = ((yPos - trackRect.y) * (max - min) / len + min);
2116 // If this isn't a legal value, then we'll have to move to one now.
2117 if (value > max)
2118 value = max;
2119 else if (value < min)
2120 value = min;
2121 return value;
2125 * This method returns the value in the slider's range given the x
2126 * coordinate. If the value is out of range, it will return the closest
2127 * legal value.
2129 * @param xPos The x coordinate to calculate a value for.
2131 * @return The value for the x coordinate.
2133 public int valueForXPosition(int xPos)
2135 int min = slider.getMinimum();
2136 int max = slider.getMaximum();
2137 int len = trackRect.width;
2139 int value;
2141 // If the length is 0, you shouldn't be able to even see where the slider is.
2142 // This really shouldn't ever happen, but just in case, we'll return the middle.
2143 if (len == 0)
2144 return ((max - min) / 2);
2146 if (! drawInverted())
2147 value = ((xPos - trackRect.x) * (max - min) / len + min);
2148 else
2149 value = ((len - (xPos - trackRect.x)) * (max - min) / len + min);
2151 // If this isn't a legal value, then we'll have to move to one now.
2152 if (value > max)
2153 value = max;
2154 else if (value < min)
2155 value = min;
2156 return value;
2160 * This method finds the closest value that has a tick associated with it.
2162 * @param value The value to search from.
2164 * @return The closest value that has a tick associated with it.
2166 private int findClosestTick(int value)
2168 int min = slider.getMinimum();
2169 int max = slider.getMaximum();
2170 int majorSpace = slider.getMajorTickSpacing();
2171 int minorSpace = slider.getMinorTickSpacing();
2173 // The default value to return is value + minor or
2174 // value + major.
2175 // Initializing at min - value leaves us with a default
2176 // return value of min, which always has tick marks
2177 // (if ticks are painted).
2178 int minor = min - value;
2179 int major = min - value;
2181 // If there are no major tick marks or minor tick marks
2182 // e.g. snap is set to true but no ticks are set, then
2183 // we can just return the value.
2184 if (majorSpace <= 0 && minorSpace <= 0)
2185 return value;
2187 // First check the major ticks.
2188 if (majorSpace > 0)
2190 int lowerBound = (value - min) / majorSpace;
2191 int majLower = majorSpace * lowerBound + min;
2192 int majHigher = majorSpace * (lowerBound + 1) + min;
2194 if (majHigher <= max && majHigher - value <= value - majLower)
2195 major = majHigher - value;
2196 else
2197 major = majLower - value;
2200 if (minorSpace > 0)
2202 int lowerBound = value / minorSpace;
2203 int minLower = minorSpace * lowerBound;
2204 int minHigher = minorSpace * (lowerBound + 1);
2206 if (minHigher <= max && minHigher - value <= value - minLower)
2207 minor = minHigher - value;
2208 else
2209 minor = minLower - value;
2212 // Give preference to minor ticks
2213 if (Math.abs(minor) > Math.abs(major))
2214 return value + major;
2215 else
2216 return value + minor;