1 /*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package javax.swing.plaf.basic;
27
28 import java.awt.Component;
29 import java.awt.Container;
30 import java.awt.Adjustable;
31 import java.awt.event;
32 import java.awt.FontMetrics;
33 import java.awt.Graphics;
34 import java.awt.Dimension;
35 import java.awt.Rectangle;
36 import java.awt.Point;
37 import java.awt.Insets;
38 import java.awt.Color;
39 import java.awt.IllegalComponentStateException;
40 import java.awt.Polygon;
41 import java.beans;
42 import java.util.Dictionary;
43 import java.util.Enumeration;
44
45 import javax.swing.border.AbstractBorder;
46
47 import javax.swing;
48 import javax.swing.event;
49 import javax.swing.plaf;
50 import sun.swing.DefaultLookup;
51 import sun.swing.UIAction;
52
53
54 /**
55 * A Basic L&F implementation of SliderUI.
56 *
57 * @author Tom Santos
58 */
59 public class BasicSliderUI extends SliderUI{
60 // Old actions forward to an instance of this.
61 private static final Actions SHARED_ACTION = new Actions();
62
63 public static final int POSITIVE_SCROLL = +1;
64 public static final int NEGATIVE_SCROLL = -1;
65 public static final int MIN_SCROLL = -2;
66 public static final int MAX_SCROLL = +2;
67
68 protected Timer scrollTimer;
69 protected JSlider slider;
70
71 protected Insets focusInsets = null;
72 protected Insets insetCache = null;
73 protected boolean leftToRightCache = true;
74 protected Rectangle focusRect = null;
75 protected Rectangle contentRect = null;
76 protected Rectangle labelRect = null;
77 protected Rectangle tickRect = null;
78 protected Rectangle trackRect = null;
79 protected Rectangle thumbRect = null;
80
81 protected int trackBuffer = 0; // The distance that the track is from the side of the control
82
83 private transient boolean isDragging;
84
85 protected TrackListener trackListener;
86 protected ChangeListener changeListener;
87 protected ComponentListener componentListener;
88 protected FocusListener focusListener;
89 protected ScrollListener scrollListener;
90 protected PropertyChangeListener propertyChangeListener;
91 private Handler handler;
92 private int lastValue;
93
94 // Colors
95 private Color shadowColor;
96 private Color highlightColor;
97 private Color focusColor;
98
99 /**
100 * Whther or not sameLabelBaselines is up to date.
101 */
102 private boolean checkedLabelBaselines;
103 /**
104 * Whether or not all the entries in the labeltable have the same
105 * baseline.
106 */
107 private boolean sameLabelBaselines;
108
109
110 protected Color getShadowColor() {
111 return shadowColor;
112 }
113
114 protected Color getHighlightColor() {
115 return highlightColor;
116 }
117
118 protected Color getFocusColor() {
119 return focusColor;
120 }
121
122 /**
123 * Returns true if the user is dragging the slider.
124 *
125 * @return true if the user is dragging the slider
126 * @since 1.5
127 */
128 protected boolean isDragging() {
129 return isDragging;
130 }
131
132 /////////////////////////////////////////////////////////////////////////////
133 // ComponentUI Interface Implementation methods
134 /////////////////////////////////////////////////////////////////////////////
135 public static ComponentUI createUI(JComponent b) {
136 return new BasicSliderUI((JSlider)b);
137 }
138
139 public BasicSliderUI(JSlider b) {
140 }
141
142 public void installUI(JComponent c) {
143 slider = (JSlider) c;
144
145 checkedLabelBaselines = false;
146
147 slider.setEnabled(slider.isEnabled());
148 LookAndFeel.installProperty(slider, "opaque", Boolean.TRUE);
149
150 isDragging = false;
151 trackListener = createTrackListener( slider );
152 changeListener = createChangeListener( slider );
153 componentListener = createComponentListener( slider );
154 focusListener = createFocusListener( slider );
155 scrollListener = createScrollListener( slider );
156 propertyChangeListener = createPropertyChangeListener( slider );
157
158 installDefaults( slider );
159 installListeners( slider );
160 installKeyboardActions( slider );
161
162 scrollTimer = new Timer( 100, scrollListener );
163 scrollTimer.setInitialDelay( 300 );
164
165 insetCache = slider.getInsets();
166 leftToRightCache = BasicGraphicsUtils.isLeftToRight(slider);
167 focusRect = new Rectangle();
168 contentRect = new Rectangle();
169 labelRect = new Rectangle();
170 tickRect = new Rectangle();
171 trackRect = new Rectangle();
172 thumbRect = new Rectangle();
173 lastValue = slider.getValue();
174
175 calculateGeometry(); // This figures out where the labels, ticks, track, and thumb are.
176 }
177
178 public void uninstallUI(JComponent c) {
179 if ( c != slider )
180 throw new IllegalComponentStateException(
181 this + " was asked to deinstall() "
182 + c + " when it only knows about "
183 + slider + ".");
184
185 LookAndFeel.uninstallBorder(slider);
186
187 scrollTimer.stop();
188 scrollTimer = null;
189
190 uninstallListeners( slider );
191 uninstallKeyboardActions(slider);
192
193 focusInsets = null;
194 insetCache = null;
195 leftToRightCache = true;
196 focusRect = null;
197 contentRect = null;
198 labelRect = null;
199 tickRect = null;
200 trackRect = null;
201 thumbRect = null;
202 trackListener = null;
203 changeListener = null;
204 componentListener = null;
205 focusListener = null;
206 scrollListener = null;
207 propertyChangeListener = null;
208 slider = null;
209 }
210
211 protected void installDefaults( JSlider slider ) {
212 LookAndFeel.installBorder(slider, "Slider.border");
213 LookAndFeel.installColorsAndFont(slider, "Slider.background",
214 "Slider.foreground", "Slider.font");
215 highlightColor = UIManager.getColor("Slider.highlight");
216
217 shadowColor = UIManager.getColor("Slider.shadow");
218 focusColor = UIManager.getColor("Slider.focus");
219
220 focusInsets = (Insets)UIManager.get( "Slider.focusInsets" );
221 }
222
223 protected TrackListener createTrackListener(JSlider slider) {
224 return new TrackListener();
225 }
226
227 protected ChangeListener createChangeListener(JSlider slider) {
228 return getHandler();
229 }
230
231 protected ComponentListener createComponentListener(JSlider slider) {
232 return getHandler();
233 }
234
235 protected FocusListener createFocusListener(JSlider slider) {
236 return getHandler();
237 }
238
239 protected ScrollListener createScrollListener( JSlider slider ) {
240 return new ScrollListener();
241 }
242
243 protected PropertyChangeListener createPropertyChangeListener(
244 JSlider slider) {
245 return getHandler();
246 }
247
248 private Handler getHandler() {
249 if (handler == null) {
250 handler = new Handler();
251 }
252 return handler;
253 }
254
255 protected void installListeners( JSlider slider ) {
256 slider.addMouseListener(trackListener);
257 slider.addMouseMotionListener(trackListener);
258 slider.addFocusListener(focusListener);
259 slider.addComponentListener(componentListener);
260 slider.addPropertyChangeListener( propertyChangeListener );
261 slider.getModel().addChangeListener(changeListener);
262 }
263
264 protected void uninstallListeners( JSlider slider ) {
265 slider.removeMouseListener(trackListener);
266 slider.removeMouseMotionListener(trackListener);
267 slider.removeFocusListener(focusListener);
268 slider.removeComponentListener(componentListener);
269 slider.removePropertyChangeListener( propertyChangeListener );
270 slider.getModel().removeChangeListener(changeListener);
271 handler = null;
272 }
273
274 protected void installKeyboardActions( JSlider slider ) {
275 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider);
276 SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED, km);
277 LazyActionMap.installLazyActionMap(slider, BasicSliderUI.class,
278 "Slider.actionMap");
279 }
280
281 InputMap getInputMap(int condition, JSlider slider) {
282 if (condition == JComponent.WHEN_FOCUSED) {
283 InputMap keyMap = (InputMap)DefaultLookup.get(slider, this,
284 "Slider.focusInputMap");
285 InputMap rtlKeyMap;
286
287 if (slider.getComponentOrientation().isLeftToRight() ||
288 ((rtlKeyMap = (InputMap)DefaultLookup.get(slider, this,
289 "Slider.focusInputMap.RightToLeft")) == null)) {
290 return keyMap;
291 } else {
292 rtlKeyMap.setParent(keyMap);
293 return rtlKeyMap;
294 }
295 }
296 return null;
297 }
298
299 /**
300 * Populates ComboBox's actions.
301 */
302 static void loadActionMap(LazyActionMap map) {
303 map.put(new Actions(Actions.POSITIVE_UNIT_INCREMENT));
304 map.put(new Actions(Actions.POSITIVE_BLOCK_INCREMENT));
305 map.put(new Actions(Actions.NEGATIVE_UNIT_INCREMENT));
306 map.put(new Actions(Actions.NEGATIVE_BLOCK_INCREMENT));
307 map.put(new Actions(Actions.MIN_SCROLL_INCREMENT));
308 map.put(new Actions(Actions.MAX_SCROLL_INCREMENT));
309 }
310
311 protected void uninstallKeyboardActions( JSlider slider ) {
312 SwingUtilities.replaceUIActionMap(slider, null);
313 SwingUtilities.replaceUIInputMap(slider, JComponent.WHEN_FOCUSED,
314 null);
315 }
316
317
318 /**
319 * Returns the baseline.
320 *
321 * @throws NullPointerException {@inheritDoc}
322 * @throws IllegalArgumentException {@inheritDoc}
323 * @see javax.swing.JComponent#getBaseline(int, int)
324 * @since 1.6
325 */
326 public int getBaseline(JComponent c, int width, int height) {
327 super.getBaseline(c, width, height);
328 if (slider.getPaintLabels() && labelsHaveSameBaselines()) {
329 FontMetrics metrics = slider.getFontMetrics(slider.getFont());
330 Insets insets = slider.getInsets();
331 Dimension thumbSize = getThumbSize();
332 if (slider.getOrientation() == JSlider.HORIZONTAL) {
333 int tickLength = getTickLength();
334 int contentHeight = height - insets.top - insets.bottom -
335 focusInsets.top - focusInsets.bottom;
336 int thumbHeight = thumbSize.height;
337 int centerSpacing = thumbHeight;
338 if (slider.getPaintTicks()) {
339 centerSpacing += tickLength;
340 }
341 // Assume uniform labels.
342 centerSpacing += getHeightOfTallestLabel();
343 int trackY = insets.top + focusInsets.top +
344 (contentHeight - centerSpacing - 1) / 2;
345 int trackHeight = thumbHeight;
346 int tickY = trackY + trackHeight;
347 int tickHeight = tickLength;
348 if (!slider.getPaintTicks()) {
349 tickHeight = 0;
350 }
351 int labelY = tickY + tickHeight;
352 return labelY + metrics.getAscent();
353 }
354 else { // vertical
355 boolean inverted = slider.getInverted();
356 Integer value = inverted ? getLowestValue() :
357 getHighestValue();
358 if (value != null) {
359 int thumbHeight = thumbSize.height;
360 int trackBuffer = Math.max(metrics.getHeight() / 2,
361 thumbHeight / 2);
362 int contentY = focusInsets.top + insets.top;
363 int trackY = contentY + trackBuffer;
364 int trackHeight = height - focusInsets.top -
365 focusInsets.bottom - insets.top - insets.bottom -
366 trackBuffer - trackBuffer;
367 int yPosition = yPositionForValue(value, trackY,
368 trackHeight);
369 return yPosition - metrics.getHeight() / 2 +
370 metrics.getAscent();
371 }
372 }
373 }
374 return 0;
375 }
376
377 /**
378 * Returns an enum indicating how the baseline of the component
379 * changes as the size changes.
380 *
381 * @throws NullPointerException {@inheritDoc}
382 * @see javax.swing.JComponent#getBaseline(int, int)
383 * @since 1.6
384 */
385 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
386 JComponent c) {
387 super.getBaselineResizeBehavior(c);
388 // NOTE: BasicSpinner really provides for CENTER_OFFSET, but
389 // the default min/pref size is smaller than it should be
390 // so that getBaseline() doesn't implement the contract
391 // for CENTER_OFFSET as defined in Component.
392 return Component.BaselineResizeBehavior.OTHER;
393 }
394
395 /**
396 * Returns true if all the labels from the label table have the same
397 * baseline.
398 *
399 * @return true if all the labels from the label table have the
400 * same baseline
401 * @since 1.6
402 */
403 protected boolean labelsHaveSameBaselines() {
404 if (!checkedLabelBaselines) {
405 checkedLabelBaselines = true;
406 Dictionary dictionary = slider.getLabelTable();
407 if (dictionary != null) {
408 sameLabelBaselines = true;
409 Enumeration elements = dictionary.elements();
410 int baseline = -1;
411 while (elements.hasMoreElements()) {
412 Component label = (Component)elements.nextElement();
413 Dimension pref = label.getPreferredSize();
414 int labelBaseline = label.getBaseline(pref.width,
415 pref.height);
416 if (labelBaseline >= 0) {
417 if (baseline == -1) {
418 baseline = labelBaseline;
419 }
420 else if (baseline != labelBaseline) {
421 sameLabelBaselines = false;
422 break;
423 }
424 }
425 else {
426 sameLabelBaselines = false;
427 break;
428 }
429 }
430 }
431 else {
432 sameLabelBaselines = false;
433 }
434 }
435 return sameLabelBaselines;
436 }
437
438 public Dimension getPreferredHorizontalSize() {
439 Dimension horizDim = (Dimension)DefaultLookup.get(slider,
440 this, "Slider.horizontalSize");
441 if (horizDim == null) {
442 horizDim = new Dimension(200, 21);
443 }
444 return horizDim;
445 }
446
447 public Dimension getPreferredVerticalSize() {
448 Dimension vertDim = (Dimension)DefaultLookup.get(slider,
449 this, "Slider.verticalSize");
450 if (vertDim == null) {
451 vertDim = new Dimension(21, 200);
452 }
453 return vertDim;
454 }
455
456 public Dimension getMinimumHorizontalSize() {
457 Dimension minHorizDim = (Dimension)DefaultLookup.get(slider,
458 this, "Slider.minimumHorizontalSize");
459 if (minHorizDim == null) {
460 minHorizDim = new Dimension(36, 21);
461 }
462 return minHorizDim;
463 }
464
465 public Dimension getMinimumVerticalSize() {
466 Dimension minVertDim = (Dimension)DefaultLookup.get(slider,
467 this, "Slider.minimumVerticalSize");
468 if (minVertDim == null) {
469 minVertDim = new Dimension(21, 36);
470 }
471 return minVertDim;
472 }
473
474 public Dimension getPreferredSize(JComponent c) {
475 recalculateIfInsetsChanged();
476 Dimension d;
477 if ( slider.getOrientation() == JSlider.VERTICAL ) {
478 d = new Dimension(getPreferredVerticalSize());
479 d.width = insetCache.left + insetCache.right;
480 d.width += focusInsets.left + focusInsets.right;
481 d.width += trackRect.width + tickRect.width + labelRect.width;
482 }
483 else {
484 d = new Dimension(getPreferredHorizontalSize());
485 d.height = insetCache.top + insetCache.bottom;
486 d.height += focusInsets.top + focusInsets.bottom;
487 d.height += trackRect.height + tickRect.height + labelRect.height;
488 }
489
490 return d;
491 }
492
493 public Dimension getMinimumSize(JComponent c) {
494 recalculateIfInsetsChanged();
495 Dimension d;
496
497 if ( slider.getOrientation() == JSlider.VERTICAL ) {
498 d = new Dimension(getMinimumVerticalSize());
499 d.width = insetCache.left + insetCache.right;
500 d.width += focusInsets.left + focusInsets.right;
501 d.width += trackRect.width + tickRect.width + labelRect.width;
502 }
503 else {
504 d = new Dimension(getMinimumHorizontalSize());
505 d.height = insetCache.top + insetCache.bottom;
506 d.height += focusInsets.top + focusInsets.bottom;
507 d.height += trackRect.height + tickRect.height + labelRect.height;
508 }
509
510 return d;
511 }
512
513 public Dimension getMaximumSize(JComponent c) {
514 Dimension d = getPreferredSize(c);
515 if ( slider.getOrientation() == JSlider.VERTICAL ) {
516 d.height = Short.MAX_VALUE;
517 }
518 else {
519 d.width = Short.MAX_VALUE;
520 }
521
522 return d;
523 }
524
525 protected void calculateGeometry() {
526 calculateFocusRect();
527 calculateContentRect();
528 calculateThumbSize();
529 calculateTrackBuffer();
530 calculateTrackRect();
531 calculateTickRect();
532 calculateLabelRect();
533 calculateThumbLocation();
534 }
535
536 protected void calculateFocusRect() {
537 focusRect.x = insetCache.left;
538 focusRect.y = insetCache.top;
539 focusRect.width = slider.getWidth() - (insetCache.left + insetCache.right);
540 focusRect.height = slider.getHeight() - (insetCache.top + insetCache.bottom);
541 }
542
543 protected void calculateThumbSize() {
544 Dimension size = getThumbSize();
545 thumbRect.setSize( size.width, size.height );
546 }
547
548 protected void calculateContentRect() {
549 contentRect.x = focusRect.x + focusInsets.left;
550 contentRect.y = focusRect.y + focusInsets.top;
551 contentRect.width = focusRect.width - (focusInsets.left + focusInsets.right);
552 contentRect.height = focusRect.height - (focusInsets.top + focusInsets.bottom);
553 }
554
555 private int getTickSpacing() {
556 int majorTickSpacing = slider.getMajorTickSpacing();
557 int minorTickSpacing = slider.getMinorTickSpacing();
558
559 int result;
560
561 if (minorTickSpacing > 0) {
562 result = minorTickSpacing;
563 } else if (majorTickSpacing > 0) {
564 result = majorTickSpacing;
565 } else {
566 result = 0;
567 }
568
569 return result;
570 }
571
572 protected void calculateThumbLocation() {
573 if ( slider.getSnapToTicks() ) {
574 int sliderValue = slider.getValue();
575 int snappedValue = sliderValue;
576 int tickSpacing = getTickSpacing();
577
578 if ( tickSpacing != 0 ) {
579 // If it's not on a tick, change the value
580 if ( (sliderValue - slider.getMinimum()) % tickSpacing != 0 ) {
581 float temp = (float)(sliderValue - slider.getMinimum()) / (float)tickSpacing;
582 int whichTick = Math.round( temp );
583
584 // This is the fix for the bug #6401380
585 if (temp - (int)temp == .5 && sliderValue < lastValue) {
586 whichTick --;
587 }
588 snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
589 }
590
591 if( snappedValue != sliderValue ) {
592 slider.setValue( snappedValue );
593 }
594 }
595 }
596
597 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
598 int valuePosition = xPositionForValue(slider.getValue());
599
600 thumbRect.x = valuePosition - (thumbRect.width / 2);
601 thumbRect.y = trackRect.y;
602 }
603 else {
604 int valuePosition = yPositionForValue(slider.getValue());
605
606 thumbRect.x = trackRect.x;
607 thumbRect.y = valuePosition - (thumbRect.height / 2);
608 }
609 }
610
611 protected void calculateTrackBuffer() {
612 if ( slider.getPaintLabels() && slider.getLabelTable() != null ) {
613 Component highLabel = getHighestValueLabel();
614 Component lowLabel = getLowestValueLabel();
615
616 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
617 trackBuffer = Math.max( highLabel.getBounds().width, lowLabel.getBounds().width ) / 2;
618 trackBuffer = Math.max( trackBuffer, thumbRect.width / 2 );
619 }
620 else {
621 trackBuffer = Math.max( highLabel.getBounds().height, lowLabel.getBounds().height ) / 2;
622 trackBuffer = Math.max( trackBuffer, thumbRect.height / 2 );
623 }
624 }
625 else {
626 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
627 trackBuffer = thumbRect.width / 2;
628 }
629 else {
630 trackBuffer = thumbRect.height / 2;
631 }
632 }
633 }
634
635
636 protected void calculateTrackRect() {
637 int centerSpacing = 0; // used to center sliders added using BorderLayout.CENTER (bug 4275631)
638 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
639 centerSpacing = thumbRect.height;
640 if ( slider.getPaintTicks() ) centerSpacing += getTickLength();
641 if ( slider.getPaintLabels() ) centerSpacing += getHeightOfTallestLabel();
642 trackRect.x = contentRect.x + trackBuffer;
643 trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1)/2;
644 trackRect.width = contentRect.width - (trackBuffer * 2);
645 trackRect.height = thumbRect.height;
646 }
647 else {
648 centerSpacing = thumbRect.width;
649 if (BasicGraphicsUtils.isLeftToRight(slider)) {
650 if ( slider.getPaintTicks() ) centerSpacing += getTickLength();
651 if ( slider.getPaintLabels() ) centerSpacing += getWidthOfWidestLabel();
652 } else {
653 if ( slider.getPaintTicks() ) centerSpacing -= getTickLength();
654 if ( slider.getPaintLabels() ) centerSpacing -= getWidthOfWidestLabel();
655 }
656 trackRect.x = contentRect.x + (contentRect.width - centerSpacing - 1)/2;
657 trackRect.y = contentRect.y + trackBuffer;
658 trackRect.width = thumbRect.width;
659 trackRect.height = contentRect.height - (trackBuffer * 2);
660 }
661
662 }
663
664 /**
665 * Gets the height of the tick area for horizontal sliders and the width of the
666 * tick area for vertical sliders. BasicSliderUI uses the returned value to
667 * determine the tick area rectangle. If you want to give your ticks some room,
668 * make this larger than you need and paint your ticks away from the sides in paintTicks().
669 */
670 protected int getTickLength() {
671 return 8;
672 }
673
674 protected void calculateTickRect() {
675 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
676 tickRect.x = trackRect.x;
677 tickRect.y = trackRect.y + trackRect.height;
678 tickRect.width = trackRect.width;
679 tickRect.height = (slider.getPaintTicks()) ? getTickLength() : 0;
680 }
681 else {
682 tickRect.width = (slider.getPaintTicks()) ? getTickLength() : 0;
683 if(BasicGraphicsUtils.isLeftToRight(slider)) {
684 tickRect.x = trackRect.x + trackRect.width;
685 }
686 else {
687 tickRect.x = trackRect.x - tickRect.width;
688 }
689 tickRect.y = trackRect.y;
690 tickRect.height = trackRect.height;
691 }
692 }
693
694 protected void calculateLabelRect() {
695 if ( slider.getPaintLabels() ) {
696 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
697 labelRect.x = tickRect.x - trackBuffer;
698 labelRect.y = tickRect.y + tickRect.height;
699 labelRect.width = tickRect.width + (trackBuffer * 2);
700 labelRect.height = getHeightOfTallestLabel();
701 }
702 else {
703 if(BasicGraphicsUtils.isLeftToRight(slider)) {
704 labelRect.x = tickRect.x + tickRect.width;
705 labelRect.width = getWidthOfWidestLabel();
706 }
707 else {
708 labelRect.width = getWidthOfWidestLabel();
709 labelRect.x = tickRect.x - labelRect.width;
710 }
711 labelRect.y = tickRect.y - trackBuffer;
712 labelRect.height = tickRect.height + (trackBuffer * 2);
713 }
714 }
715 else {
716 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
717 labelRect.x = tickRect.x;
718 labelRect.y = tickRect.y + tickRect.height;
719 labelRect.width = tickRect.width;
720 labelRect.height = 0;
721 }
722 else {
723 if(BasicGraphicsUtils.isLeftToRight(slider)) {
724 labelRect.x = tickRect.x + tickRect.width;
725 }
726 else {
727 labelRect.x = tickRect.x;
728 }
729 labelRect.y = tickRect.y;
730 labelRect.width = 0;
731 labelRect.height = tickRect.height;
732 }
733 }
734 }
735
736 protected Dimension getThumbSize() {
737 Dimension size = new Dimension();
738
739 if ( slider.getOrientation() == JSlider.VERTICAL ) {
740 size.width = 20;
741 size.height = 11;
742 }
743 else {
744 size.width = 11;
745 size.height = 20;
746 }
747
748 return size;
749 }
750
751 public class PropertyChangeHandler implements PropertyChangeListener {
752 // NOTE: This class exists only for backward compatability. All
753 // its functionality has been moved into Handler. If you need to add
754 // new functionality add it to the Handler, but make sure this
755 // class calls into the Handler.
756 public void propertyChange( PropertyChangeEvent e ) {
757 getHandler().propertyChange(e);
758 }
759 }
760
761 protected int getWidthOfWidestLabel() {
762 Dictionary dictionary = slider.getLabelTable();
763 int widest = 0;
764 if ( dictionary != null ) {
765 Enumeration keys = dictionary.keys();
766 while ( keys.hasMoreElements() ) {
767 Component label = (Component)dictionary.get( keys.nextElement() );
768 widest = Math.max( label.getPreferredSize().width, widest );
769 }
770 }
771 return widest;
772 }
773
774 protected int getHeightOfTallestLabel() {
775 Dictionary dictionary = slider.getLabelTable();
776 int tallest = 0;
777 if ( dictionary != null ) {
778 Enumeration keys = dictionary.keys();
779 while ( keys.hasMoreElements() ) {
780 Component label = (Component)dictionary.get( keys.nextElement() );
781 tallest = Math.max( label.getPreferredSize().height, tallest );
782 }
783 }
784 return tallest;
785 }
786
787 protected int getWidthOfHighValueLabel() {
788 Component label = getHighestValueLabel();
789 int width = 0;
790
791 if ( label != null ) {
792 width = label.getPreferredSize().width;
793 }
794
795 return width;
796 }
797
798 protected int getWidthOfLowValueLabel() {
799 Component label = getLowestValueLabel();
800 int width = 0;
801
802 if ( label != null ) {
803 width = label.getPreferredSize().width;
804 }
805
806 return width;
807 }
808
809 protected int getHeightOfHighValueLabel() {
810 Component label = getHighestValueLabel();
811 int height = 0;
812
813 if ( label != null ) {
814 height = label.getPreferredSize().height;
815 }
816
817 return height;
818 }
819
820 protected int getHeightOfLowValueLabel() {
821 Component label = getLowestValueLabel();
822 int height = 0;
823
824 if ( label != null ) {
825 height = label.getPreferredSize().height;
826 }
827
828 return height;
829 }
830
831 protected boolean drawInverted() {
832 if (slider.getOrientation()==JSlider.HORIZONTAL) {
833 if(BasicGraphicsUtils.isLeftToRight(slider)) {
834 return slider.getInverted();
835 } else {
836 return !slider.getInverted();
837 }
838 } else {
839 return slider.getInverted();
840 }
841 }
842
843 /**
844 * Returns the biggest value that has an entry in the label table.
845 *
846 * @return biggest value that has an entry in the label table, or
847 * null.
848 * @since 1.6
849 */
850 protected Integer getHighestValue() {
851 Dictionary dictionary = slider.getLabelTable();
852 if (dictionary != null) {
853 Enumeration keys = dictionary.keys();
854 int max = slider.getMinimum() - 1;
855 while (keys.hasMoreElements()) {
856 max = Math.max(max, ((Integer)keys.nextElement()).intValue());
857 }
858 if (max == slider.getMinimum() - 1) {
859 return null;
860 }
861 return max;
862 }
863 return null;
864 }
865
866 /**
867 * Returns the smallest value that has an entry in the label table.
868 *
869 * @return smallest value that has an entry in the label table, or
870 * null.
871 * @since 1.6
872 */
873 protected Integer getLowestValue() {
874 Dictionary dictionary = slider.getLabelTable();
875 if (dictionary != null) {
876 Enumeration keys = dictionary.keys();
877 int min = slider.getMaximum() + 1;
878 while (keys.hasMoreElements()) {
879 min = Math.min(min, ((Integer)keys.nextElement()).intValue());
880 }
881 if (min == slider.getMaximum() + 1) {
882 return null;
883 }
884 return min;
885 }
886 return null;
887 }
888
889
890 /**
891 * Returns the label that corresponds to the highest slider value in the label table.
892 * @see JSlider#setLabelTable
893 */
894 protected Component getLowestValueLabel() {
895 Integer min = getLowestValue();
896 if (min != null) {
897 return (Component)slider.getLabelTable().get(min);
898 }
899 return null;
900 }
901
902 /**
903 * Returns the label that corresponds to the lowest slider value in the label table.
904 * @see JSlider#setLabelTable
905 */
906 protected Component getHighestValueLabel() {
907 Integer max = getHighestValue();
908 if (max != null) {
909 return (Component)slider.getLabelTable().get(max);
910 }
911 return null;
912 }
913
914 public void paint( Graphics g, JComponent c ) {
915 recalculateIfInsetsChanged();
916 recalculateIfOrientationChanged();
917 Rectangle clip = g.getClipBounds();
918
919 if ( !clip.intersects(trackRect) && slider.getPaintTrack())
920 calculateGeometry();
921
922 if ( slider.getPaintTrack() && clip.intersects( trackRect ) ) {
923 paintTrack( g );
924 }
925 if ( slider.getPaintTicks() && clip.intersects( tickRect ) ) {
926 paintTicks( g );
927 }
928 if ( slider.getPaintLabels() && clip.intersects( labelRect ) ) {
929 paintLabels( g );
930 }
931 if ( slider.hasFocus() && clip.intersects( focusRect ) ) {
932 paintFocus( g );
933 }
934 if ( clip.intersects( thumbRect ) ) {
935 paintThumb( g );
936 }
937 }
938
939 protected void recalculateIfInsetsChanged() {
940 Insets newInsets = slider.getInsets();
941 if ( !newInsets.equals( insetCache ) ) {
942 insetCache = newInsets;
943 calculateGeometry();
944 }
945 }
946
947 protected void recalculateIfOrientationChanged() {
948 boolean ltr = BasicGraphicsUtils.isLeftToRight(slider);
949 if ( ltr!=leftToRightCache ) {
950 leftToRightCache = ltr;
951 calculateGeometry();
952 }
953 }
954
955 public void paintFocus(Graphics g) {
956 g.setColor( getFocusColor() );
957
958 BasicGraphicsUtils.drawDashedRect( g, focusRect.x, focusRect.y,
959 focusRect.width, focusRect.height );
960 }
961
962 public void paintTrack(Graphics g) {
963
964 Rectangle trackBounds = trackRect;
965
966 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
967 int cy = (trackBounds.height / 2) - 2;
968 int cw = trackBounds.width;
969
970 g.translate(trackBounds.x, trackBounds.y + cy);
971
972 g.setColor(getShadowColor());
973 g.drawLine(0, 0, cw - 1, 0);
974 g.drawLine(0, 1, 0, 2);
975 g.setColor(getHighlightColor());
976 g.drawLine(0, 3, cw, 3);
977 g.drawLine(cw, 0, cw, 3);
978 g.setColor(Color.black);
979 g.drawLine(1, 1, cw-2, 1);
980
981 g.translate(-trackBounds.x, -(trackBounds.y + cy));
982 }
983 else {
984 int cx = (trackBounds.width / 2) - 2;
985 int ch = trackBounds.height;
986
987 g.translate(trackBounds.x + cx, trackBounds.y);
988
989 g.setColor(getShadowColor());
990 g.drawLine(0, 0, 0, ch - 1);
991 g.drawLine(1, 0, 2, 0);
992 g.setColor(getHighlightColor());
993 g.drawLine(3, 0, 3, ch);
994 g.drawLine(0, ch, 3, ch);
995 g.setColor(Color.black);
996 g.drawLine(1, 1, 1, ch-2);
997
998 g.translate(-(trackBounds.x + cx), -trackBounds.y);
999 }
1000 }
1001
1002 public void paintTicks(Graphics g) {
1003 Rectangle tickBounds = tickRect;
1004 int i;
1005 int maj, min, max;
1006 int w = tickBounds.width;
1007 int h = tickBounds.height;
1008 int centerEffect, tickHeight;
1009
1010 g.setColor(DefaultLookup.getColor(slider, this, "Slider.tickColor", Color.black));
1011
1012 maj = slider.getMajorTickSpacing();
1013 min = slider.getMinorTickSpacing();
1014
1015 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1016 g.translate( 0, tickBounds.y);
1017
1018 int value = slider.getMinimum();
1019 int xPos = 0;
1020
1021 if ( slider.getMinorTickSpacing() > 0 ) {
1022 while ( value <= slider.getMaximum() ) {
1023 xPos = xPositionForValue( value );
1024 paintMinorTickForHorizSlider( g, tickBounds, xPos );
1025 value += slider.getMinorTickSpacing();
1026 }
1027 }
1028
1029 if ( slider.getMajorTickSpacing() > 0 ) {
1030 value = slider.getMinimum();
1031
1032 while ( value <= slider.getMaximum() ) {
1033 xPos = xPositionForValue( value );
1034 paintMajorTickForHorizSlider( g, tickBounds, xPos );
1035 value += slider.getMajorTickSpacing();
1036 }
1037 }
1038
1039 g.translate( 0, -tickBounds.y);
1040 }
1041 else {
1042 g.translate(tickBounds.x, 0);
1043
1044 int value = slider.getMinimum();
1045 int yPos = 0;
1046
1047 if ( slider.getMinorTickSpacing() > 0 ) {
1048 int offset = 0;
1049 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1050 offset = tickBounds.width - tickBounds.width / 2;
1051 g.translate(offset, 0);
1052 }
1053
1054 while ( value <= slider.getMaximum() ) {
1055 yPos = yPositionForValue( value );
1056 paintMinorTickForVertSlider( g, tickBounds, yPos );
1057 value += slider.getMinorTickSpacing();
1058 }
1059
1060 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1061 g.translate(-offset, 0);
1062 }
1063 }
1064
1065 if ( slider.getMajorTickSpacing() > 0 ) {
1066 value = slider.getMinimum();
1067 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1068 g.translate(2, 0);
1069 }
1070
1071 while ( value <= slider.getMaximum() ) {
1072 yPos = yPositionForValue( value );
1073 paintMajorTickForVertSlider( g, tickBounds, yPos );
1074 value += slider.getMajorTickSpacing();
1075 }
1076
1077 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1078 g.translate(-2, 0);
1079 }
1080 }
1081 g.translate(-tickBounds.x, 0);
1082 }
1083 }
1084
1085 protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1086 g.drawLine( x, 0, x, tickBounds.height / 2 - 1 );
1087 }
1088
1089 protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1090 g.drawLine( x, 0, x, tickBounds.height - 2 );
1091 }
1092
1093 protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1094 g.drawLine( 0, y, tickBounds.width / 2 - 1, y );
1095 }
1096
1097 protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1098 g.drawLine( 0, y, tickBounds.width - 2, y );
1099 }
1100
1101 public void paintLabels( Graphics g ) {
1102 Rectangle labelBounds = labelRect;
1103
1104 Dictionary dictionary = slider.getLabelTable();
1105 if ( dictionary != null ) {
1106 Enumeration keys = dictionary.keys();
1107 int minValue = slider.getMinimum();
1108 int maxValue = slider.getMaximum();
1109 boolean enabled = slider.isEnabled();
1110 while ( keys.hasMoreElements() ) {
1111 Integer key = (Integer)keys.nextElement();
1112 int value = key.intValue();
1113 if (value >= minValue && value <= maxValue) {
1114 Component label = (Component)dictionary.get( key );
1115 if (label instanceof JComponent) {
1116 ((JComponent)label).setEnabled(enabled);
1117 }
1118 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1119 g.translate( 0, labelBounds.y );
1120 paintHorizontalLabel( g, value, label );
1121 g.translate( 0, -labelBounds.y );
1122 }
1123 else {
1124 int offset = 0;
1125 if (!BasicGraphicsUtils.isLeftToRight(slider)) {
1126 offset = labelBounds.width -
1127 label.getPreferredSize().width;
1128 }
1129 g.translate( labelBounds.x + offset, 0 );
1130 paintVerticalLabel( g, value, label );
1131 g.translate( -labelBounds.x - offset, 0 );
1132 }
1133 }
1134 }
1135 }
1136
1137 }
1138
1139 /**
1140 * Called for every label in the label table. Used to draw the labels for horizontal sliders.
1141 * The graphics have been translated to labelRect.y already.
1142 * @see JSlider#setLabelTable
1143 */
1144 protected void paintHorizontalLabel( Graphics g, int value, Component label ) {
1145 int labelCenter = xPositionForValue( value );
1146 int labelLeft = labelCenter - (label.getPreferredSize().width / 2);
1147 g.translate( labelLeft, 0 );
1148 label.paint( g );
1149 g.translate( -labelLeft, 0 );
1150 }
1151
1152 /**
1153 * Called for every label in the label table. Used to draw the labels for vertical sliders.
1154 * The graphics have been translated to labelRect.x already.
1155 * @see JSlider#setLabelTable
1156 */
1157 protected void paintVerticalLabel( Graphics g, int value, Component label ) {
1158 int labelCenter = yPositionForValue( value );
1159 int labelTop = labelCenter - (label.getPreferredSize().height / 2);
1160 g.translate( 0, labelTop );
1161 label.paint( g );
1162 g.translate( 0, -labelTop );
1163 }
1164
1165 public void paintThumb(Graphics g) {
1166 Rectangle knobBounds = thumbRect;
1167 int w = knobBounds.width;
1168 int h = knobBounds.height;
1169
1170 g.translate(knobBounds.x, knobBounds.y);
1171
1172 if ( slider.isEnabled() ) {
1173 g.setColor(slider.getBackground());
1174 }
1175 else {
1176 g.setColor(slider.getBackground().darker());
1177 }
1178
1179 Boolean paintThumbArrowShape =
1180 (Boolean)slider.getClientProperty("Slider.paintThumbArrowShape");
1181
1182 if ((!slider.getPaintTicks() && paintThumbArrowShape == null) ||
1183 paintThumbArrowShape == Boolean.FALSE) {
1184
1185 // "plain" version
1186 g.fillRect(0, 0, w, h);
1187
1188 g.setColor(Color.black);
1189 g.drawLine(0, h-1, w-1, h-1);
1190 g.drawLine(w-1, 0, w-1, h-1);
1191
1192 g.setColor(highlightColor);
1193 g.drawLine(0, 0, 0, h-2);
1194 g.drawLine(1, 0, w-2, 0);
1195
1196 g.setColor(shadowColor);
1197 g.drawLine(1, h-2, w-2, h-2);
1198 g.drawLine(w-2, 1, w-2, h-3);
1199 }
1200 else if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1201 int cw = w / 2;
1202 g.fillRect(1, 1, w-3, h-1-cw);
1203 Polygon p = new Polygon();
1204 p.addPoint(1, h-cw);
1205 p.addPoint(cw-1, h-1);
1206 p.addPoint(w-2, h-1-cw);
1207 g.fillPolygon(p);
1208
1209 g.setColor(highlightColor);
1210 g.drawLine(0, 0, w-2, 0);
1211 g.drawLine(0, 1, 0, h-1-cw);
1212 g.drawLine(0, h-cw, cw-1, h-1);
1213
1214 g.setColor(Color.black);
1215 g.drawLine(w-1, 0, w-1, h-2-cw);
1216 g.drawLine(w-1, h-1-cw, w-1-cw, h-1);
1217
1218 g.setColor(shadowColor);
1219 g.drawLine(w-2, 1, w-2, h-2-cw);
1220 g.drawLine(w-2, h-1-cw, w-1-cw, h-2);
1221 }
1222 else { // vertical
1223 int cw = h / 2;
1224 if(BasicGraphicsUtils.isLeftToRight(slider)) {
1225 g.fillRect(1, 1, w-1-cw, h-3);
1226 Polygon p = new Polygon();
1227 p.addPoint(w-cw-1, 0);
1228 p.addPoint(w-1, cw);
1229 p.addPoint(w-1-cw, h-2);
1230 g.fillPolygon(p);
1231
1232 g.setColor(highlightColor);
1233 g.drawLine(0, 0, 0, h - 2); // left
1234 g.drawLine(1, 0, w-1-cw, 0); // top
1235 g.drawLine(w-cw-1, 0, w-1, cw); // top slant
1236
1237 g.setColor(Color.black);
1238 g.drawLine(0, h-1, w-2-cw, h-1); // bottom
1239 g.drawLine(w-1-cw, h-1, w-1, h-1-cw); // bottom slant
1240
1241 g.setColor(shadowColor);
1242 g.drawLine(1, h-2, w-2-cw, h-2 ); // bottom
1243 g.drawLine(w-1-cw, h-2, w-2, h-cw-1 ); // bottom slant
1244 }
1245 else {
1246 g.fillRect(5, 1, w-1-cw, h-3);
1247 Polygon p = new Polygon();
1248 p.addPoint(cw, 0);
1249 p.addPoint(0, cw);
1250 p.addPoint(cw, h-2);
1251 g.fillPolygon(p);
1252
1253 g.setColor(highlightColor);
1254 g.drawLine(cw-1, 0, w-2, 0); // top
1255 g.drawLine(0, cw, cw, 0); // top slant
1256
1257 g.setColor(Color.black);
1258 g.drawLine(0, h-1-cw, cw, h-1 ); // bottom slant
1259 g.drawLine(cw, h-1, w-1, h-1); // bottom
1260
1261 g.setColor(shadowColor);
1262 g.drawLine(cw, h-2, w-2, h-2 ); // bottom
1263 g.drawLine(w-1, 1, w-1, h-2 ); // right
1264 }
1265 }
1266
1267 g.translate(-knobBounds.x, -knobBounds.y);
1268 }
1269
1270 // Used exclusively by setThumbLocation()
1271 private static Rectangle unionRect = new Rectangle();
1272
1273 public void setThumbLocation(int x, int y) {
1274 unionRect.setBounds( thumbRect );
1275
1276 thumbRect.setLocation( x, y );
1277
1278 SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, unionRect );
1279 slider.repaint( unionRect.x, unionRect.y, unionRect.width, unionRect.height );
1280 }
1281
1282 public void scrollByBlock(int direction) {
1283 synchronized(slider) {
1284 int blockIncrement =
1285 (slider.getMaximum() - slider.getMinimum()) / 10;
1286 if (blockIncrement == 0) {
1287 blockIncrement = 1;
1288 }
1289
1290 if (slider.getSnapToTicks()) {
1291 int tickSpacing = getTickSpacing();
1292
1293 if (blockIncrement < tickSpacing) {
1294 blockIncrement = tickSpacing;
1295 }
1296 }
1297
1298 int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1299 slider.setValue(slider.getValue() + delta);
1300 }
1301 }
1302
1303 public void scrollByUnit(int direction) {
1304 synchronized(slider) {
1305 int delta = ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1306
1307 if (slider.getSnapToTicks()) {
1308 delta *= getTickSpacing();
1309 }
1310
1311 slider.setValue(slider.getValue() + delta);
1312 }
1313 }
1314
1315 /**
1316 * This function is called when a mousePressed was detected in the track, not
1317 * in the thumb. The default behavior is to scroll by block. You can
1318 * override this method to stop it from scrolling or to add additional behavior.
1319 */
1320 protected void scrollDueToClickInTrack( int dir ) {
1321 scrollByBlock( dir );
1322 }
1323
1324 protected int xPositionForValue( int value ) {
1325 int min = slider.getMinimum();
1326 int max = slider.getMaximum();
1327 int trackLength = trackRect.width;
1328 double valueRange = (double)max - (double)min;
1329 double pixelsPerValue = (double)trackLength / valueRange;
1330 int trackLeft = trackRect.x;
1331 int trackRight = trackRect.x + (trackRect.width - 1);
1332 int xPosition;
1333
1334 if ( !drawInverted() ) {
1335 xPosition = trackLeft;
1336 xPosition += Math.round( pixelsPerValue * ((double)value - min) );
1337 }
1338 else {
1339 xPosition = trackRight;
1340 xPosition -= Math.round( pixelsPerValue * ((double)value - min) );
1341 }
1342
1343 xPosition = Math.max( trackLeft, xPosition );
1344 xPosition = Math.min( trackRight, xPosition );
1345
1346 return xPosition;
1347 }
1348
1349 protected int yPositionForValue( int value ) {
1350 return yPositionForValue(value, trackRect.y, trackRect.height);
1351 }
1352
1353 /**
1354 * Returns the y location for the specified value. No checking is
1355 * done on the arguments. In particular if <code>trackHeight</code> is
1356 * negative undefined results may occur.
1357 *
1358 * @param value the slider value to get the location for
1359 * @param trackY y-origin of the track
1360 * @param trackHeight the height of the track
1361 * @since 1.6
1362 */
1363 protected int yPositionForValue(int value, int trackY, int trackHeight) {
1364 int min = slider.getMinimum();
1365 int max = slider.getMaximum();
1366 double valueRange = (double)max - (double)min;
1367 double pixelsPerValue = (double)trackHeight / (double)valueRange;
1368 int trackBottom = trackY + (trackHeight - 1);
1369 int yPosition;
1370
1371 if ( !drawInverted() ) {
1372 yPosition = trackY;
1373 yPosition += Math.round( pixelsPerValue * ((double)max - value ) );
1374 }
1375 else {
1376 yPosition = trackY;
1377 yPosition += Math.round( pixelsPerValue * ((double)value - min) );
1378 }
1379
1380 yPosition = Math.max( trackY, yPosition );
1381 yPosition = Math.min( trackBottom, yPosition );
1382
1383 return yPosition;
1384 }
1385
1386 /**
1387 * Returns a value give a y position. If yPos is past the track at the top or the
1388 * bottom it will set the value to the min or max of the slider, depending if the
1389 * slider is inverted or not.
1390 */
1391 public int valueForYPosition( int yPos ) {
1392 int value;
1393 final int minValue = slider.getMinimum();
1394 final int maxValue = slider.getMaximum();
1395 final int trackLength = trackRect.height;
1396 final int trackTop = trackRect.y;
1397 final int trackBottom = trackRect.y + (trackRect.height - 1);
1398
1399 if ( yPos <= trackTop ) {
1400 value = drawInverted() ? minValue : maxValue;
1401 }
1402 else if ( yPos >= trackBottom ) {
1403 value = drawInverted() ? maxValue : minValue;
1404 }
1405 else {
1406 int distanceFromTrackTop = yPos - trackTop;
1407 double valueRange = (double)maxValue - (double)minValue;
1408 double valuePerPixel = valueRange / (double)trackLength;
1409 int valueFromTrackTop = (int)Math.round( distanceFromTrackTop * valuePerPixel );
1410
1411 value = drawInverted() ? minValue + valueFromTrackTop : maxValue - valueFromTrackTop;
1412 }
1413
1414 return value;
1415 }
1416
1417 /**
1418 * Returns a value give an x position. If xPos is past the track at the left or the
1419 * right it will set the value to the min or max of the slider, depending if the
1420 * slider is inverted or not.
1421 */
1422 public int valueForXPosition( int xPos ) {
1423 int value;
1424 final int minValue = slider.getMinimum();
1425 final int maxValue = slider.getMaximum();
1426 final int trackLength = trackRect.width;
1427 final int trackLeft = trackRect.x;
1428 final int trackRight = trackRect.x + (trackRect.width - 1);
1429
1430 if ( xPos <= trackLeft ) {
1431 value = drawInverted() ? maxValue : minValue;
1432 }
1433 else if ( xPos >= trackRight ) {
1434 value = drawInverted() ? minValue : maxValue;
1435 }
1436 else {
1437 int distanceFromTrackLeft = xPos - trackLeft;
1438 double valueRange = (double)maxValue - (double)minValue;
1439 double valuePerPixel = valueRange / (double)trackLength;
1440 int valueFromTrackLeft = (int)Math.round( distanceFromTrackLeft * valuePerPixel );
1441
1442 value = drawInverted() ? maxValue - valueFromTrackLeft :
1443 minValue + valueFromTrackLeft;
1444 }
1445
1446 return value;
1447 }
1448
1449
1450 private class Handler implements ChangeListener,
1451 ComponentListener, FocusListener, PropertyChangeListener {
1452 // Change Handler
1453 public void stateChanged(ChangeEvent e) {
1454 if (!isDragging) {
1455 calculateThumbLocation();
1456 slider.repaint();
1457 }
1458 lastValue = slider.getValue();
1459 }
1460
1461 // Component Handler
1462 public void componentHidden(ComponentEvent e) { }
1463 public void componentMoved(ComponentEvent e) { }
1464 public void componentResized(ComponentEvent e) {
1465 calculateGeometry();
1466 slider.repaint();
1467 }
1468 public void componentShown(ComponentEvent e) { }
1469
1470 // Focus Handler
1471 public void focusGained(FocusEvent e) { slider.repaint(); }
1472 public void focusLost(FocusEvent e) { slider.repaint(); }
1473
1474 // Property Change Handler
1475 public void propertyChange(PropertyChangeEvent e) {
1476 String propertyName = e.getPropertyName();
1477 if (propertyName == "orientation" ||
1478 propertyName == "inverted" ||
1479 propertyName == "labelTable" ||
1480 propertyName == "majorTickSpacing" ||
1481 propertyName == "minorTickSpacing" ||
1482 propertyName == "paintTicks" ||
1483 propertyName == "paintTrack" ||
1484 propertyName == "font" ||
1485 propertyName == "paintLabels") {
1486 checkedLabelBaselines = false;
1487 calculateGeometry();
1488 slider.repaint();
1489 } else if (propertyName == "componentOrientation") {
1490 calculateGeometry();
1491 slider.repaint();
1492 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider);
1493 SwingUtilities.replaceUIInputMap(slider,
1494 JComponent.WHEN_FOCUSED, km);
1495 } else if (propertyName == "model") {
1496 ((BoundedRangeModel)e.getOldValue()).removeChangeListener(
1497 changeListener);
1498 ((BoundedRangeModel)e.getNewValue()).addChangeListener(
1499 changeListener);
1500 calculateThumbLocation();
1501 slider.repaint();
1502 }
1503 }
1504 }
1505
1506 /////////////////////////////////////////////////////////////////////////
1507 /// Model Listener Class
1508 /////////////////////////////////////////////////////////////////////////
1509 /**
1510 * Data model listener.
1511 *
1512 * This class should be treated as a "protected" inner class.
1513 * Instantiate it only within subclasses of <Foo>.
1514 */
1515 public class ChangeHandler implements ChangeListener {
1516 // NOTE: This class exists only for backward compatability. All
1517 // its functionality has been moved into Handler. If you need to add
1518 // new functionality add it to the Handler, but make sure this
1519 // class calls into the Handler.
1520 public void stateChanged(ChangeEvent e) {
1521 getHandler().stateChanged(e);
1522 }
1523 }
1524
1525 /////////////////////////////////////////////////////////////////////////
1526 /// Track Listener Class
1527 /////////////////////////////////////////////////////////////////////////
1528 /**
1529 * Track mouse movements.
1530 *
1531 * This class should be treated as a "protected" inner class.
1532 * Instantiate it only within subclasses of <Foo>.
1533 */
1534 public class TrackListener extends MouseInputAdapter {
1535 protected transient int offset;
1536 protected transient int currentMouseX, currentMouseY;
1537
1538 public void mouseReleased(MouseEvent e) {
1539 if (!slider.isEnabled()) {
1540 return;
1541 }
1542
1543 offset = 0;
1544 scrollTimer.stop();
1545
1546 // This is the way we have to determine snap-to-ticks. It's
1547 // hard to explain but since ChangeEvents don't give us any
1548 // idea what has changed we don't have a way to stop the thumb
1549 // bounds from being recalculated. Recalculating the thumb
1550 // bounds moves the thumb over the current value (i.e., snapping
1551 // to the ticks).
1552 if (slider.getSnapToTicks() /*|| slider.getSnapToValue()*/ ) {
1553 isDragging = false;
1554 slider.setValueIsAdjusting(false);
1555 }
1556 else {
1557 slider.setValueIsAdjusting(false);
1558 isDragging = false;
1559 }
1560 slider.repaint();
1561 }
1562
1563 /**
1564 * If the mouse is pressed above the "thumb" component
1565 * then reduce the scrollbars value by one page ("page up"),
1566 * otherwise increase it by one page. If there is no
1567 * thumb then page up if the mouse is in the upper half
1568 * of the track.
1569 */
1570 public void mousePressed(MouseEvent e) {
1571 if (!slider.isEnabled()) {
1572 return;
1573 }
1574
1575 // We should recalculate geometry just before
1576 // calculation of the thumb movement direction.
1577 // It is important for the case, when JSlider
1578 // is a cell editor in JTable. See 6348946.
1579 calculateGeometry();
1580
1581 currentMouseX = e.getX();
1582 currentMouseY = e.getY();
1583
1584 if (slider.isRequestFocusEnabled()) {
1585 slider.requestFocus();
1586 }
1587
1588 // Clicked in the Thumb area?
1589 if (thumbRect.contains(currentMouseX, currentMouseY)) {
1590 if (UIManager.getBoolean("Slider.onlyLeftMouseButtonDrag")
1591 && !SwingUtilities.isLeftMouseButton(e)) {
1592 return;
1593 }
1594
1595 switch (slider.getOrientation()) {
1596 case JSlider.VERTICAL:
1597 offset = currentMouseY - thumbRect.y;
1598 break;
1599 case JSlider.HORIZONTAL:
1600 offset = currentMouseX - thumbRect.x;
1601 break;
1602 }
1603 isDragging = true;
1604 return;
1605 }
1606
1607 if (!SwingUtilities.isLeftMouseButton(e)) {
1608 return;
1609 }
1610
1611 isDragging = false;
1612 slider.setValueIsAdjusting(true);
1613
1614 Dimension sbSize = slider.getSize();
1615 int direction = POSITIVE_SCROLL;
1616
1617 switch (slider.getOrientation()) {
1618 case JSlider.VERTICAL:
1619 if ( thumbRect.isEmpty() ) {
1620 int scrollbarCenter = sbSize.height / 2;
1621 if ( !drawInverted() ) {
1622 direction = (currentMouseY < scrollbarCenter) ?
1623 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1624 }
1625 else {
1626 direction = (currentMouseY < scrollbarCenter) ?
1627 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1628 }
1629 }
1630 else {
1631 int thumbY = thumbRect.y;
1632 if ( !drawInverted() ) {
1633 direction = (currentMouseY < thumbY) ?
1634 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1635 }
1636 else {
1637 direction = (currentMouseY < thumbY) ?
1638 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1639 }
1640 }
1641 break;
1642 case JSlider.HORIZONTAL:
1643 if ( thumbRect.isEmpty() ) {
1644 int scrollbarCenter = sbSize.width / 2;
1645 if ( !drawInverted() ) {
1646 direction = (currentMouseX < scrollbarCenter) ?
1647 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1648 }
1649 else {
1650 direction = (currentMouseX < scrollbarCenter) ?
1651 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1652 }
1653 }
1654 else {
1655 int thumbX = thumbRect.x;
1656 if ( !drawInverted() ) {
1657 direction = (currentMouseX < thumbX) ?
1658 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1659 }
1660 else {
1661 direction = (currentMouseX < thumbX) ?
1662 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1663 }
1664 }
1665 break;
1666 }
1667
1668 if (shouldScroll(direction)) {
1669 scrollDueToClickInTrack(direction);
1670 }
1671 if (shouldScroll(direction)) {
1672 scrollTimer.stop();
1673 scrollListener.setDirection(direction);
1674 scrollTimer.start();
1675 }
1676 }
1677
1678 public boolean shouldScroll(int direction) {
1679 Rectangle r = thumbRect;
1680 if (slider.getOrientation() == JSlider.VERTICAL) {
1681 if (drawInverted() ? direction < 0 : direction > 0) {
1682 if (r.y <= currentMouseY) {
1683 return false;
1684 }
1685 }
1686 else if (r.y + r.height >= currentMouseY) {
1687 return false;
1688 }
1689 }
1690 else {
1691 if (drawInverted() ? direction < 0 : direction > 0) {
1692 if (r.x + r.width >= currentMouseX) {
1693 return false;
1694 }
1695 }
1696 else if (r.x <= currentMouseX) {
1697 return false;
1698 }
1699 }
1700
1701 if (direction > 0 && slider.getValue() + slider.getExtent() >=
1702 slider.getMaximum()) {
1703 return false;
1704 }
1705 else if (direction < 0 && slider.getValue() <=
1706 slider.getMinimum()) {
1707 return false;
1708 }
1709
1710 return true;
1711 }
1712
1713 /**
1714 * Set the models value to the position of the top/left
1715 * of the thumb relative to the origin of the track.
1716 */
1717 public void mouseDragged(MouseEvent e) {
1718 int thumbMiddle = 0;
1719
1720 if (!slider.isEnabled()) {
1721 return;
1722 }
1723
1724 currentMouseX = e.getX();
1725 currentMouseY = e.getY();
1726
1727 if (!isDragging) {
1728 return;
1729 }
1730
1731 slider.setValueIsAdjusting(true);
1732
1733 switch (slider.getOrientation()) {
1734 case JSlider.VERTICAL:
1735 int halfThumbHeight = thumbRect.height / 2;
1736 int thumbTop = e.getY() - offset;
1737 int trackTop = trackRect.y;
1738 int trackBottom = trackRect.y + (trackRect.height - 1);
1739 int vMax = yPositionForValue(slider.getMaximum() -
1740 slider.getExtent());
1741
1742 if (drawInverted()) {
1743 trackBottom = vMax;
1744 }
1745 else {
1746 trackTop = vMax;
1747 }
1748 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
1749 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
1750
1751 setThumbLocation(thumbRect.x, thumbTop);
1752
1753 thumbMiddle = thumbTop + halfThumbHeight;
1754 slider.setValue( valueForYPosition( thumbMiddle ) );
1755 break;
1756 case JSlider.HORIZONTAL:
1757 int halfThumbWidth = thumbRect.width / 2;
1758 int thumbLeft = e.getX() - offset;
1759 int trackLeft = trackRect.x;
1760 int trackRight = trackRect.x + (trackRect.width - 1);
1761 int hMax = xPositionForValue(slider.getMaximum() -
1762 slider.getExtent());
1763
1764 if (drawInverted()) {
1765 trackLeft = hMax;
1766 }
1767 else {
1768 trackRight = hMax;
1769 }
1770 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
1771 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
1772
1773 setThumbLocation(thumbLeft, thumbRect.y);
1774
1775 thumbMiddle = thumbLeft + halfThumbWidth;
1776 slider.setValue(valueForXPosition(thumbMiddle));
1777 break;
1778 default:
1779 return;
1780 }
1781 }
1782
1783 public void mouseMoved(MouseEvent e) { }
1784 }
1785
1786 /**
1787 * Scroll-event listener.
1788 *
1789 * This class should be treated as a "protected" inner class.
1790 * Instantiate it only within subclasses of <Foo>.
1791 */
1792 public class ScrollListener implements ActionListener {
1793 // changed this class to public to avoid bogus IllegalAccessException
1794 // bug in InternetExplorer browser. It was protected. Work around
1795 // for 4109432
1796 int direction = POSITIVE_SCROLL;
1797 boolean useBlockIncrement;
1798
1799 public ScrollListener() {
1800 direction = POSITIVE_SCROLL;
1801 useBlockIncrement = true;
1802 }
1803
1804 public ScrollListener(int dir, boolean block) {
1805 direction = dir;
1806 useBlockIncrement = block;
1807 }
1808
1809 public void setDirection(int direction) {
1810 this.direction = direction;
1811 }
1812
1813 public void setScrollByBlock(boolean block) {
1814 this.useBlockIncrement = block;
1815 }
1816
1817 public void actionPerformed(ActionEvent e) {
1818 if (useBlockIncrement) {
1819 scrollByBlock(direction);
1820 }
1821 else {
1822 scrollByUnit(direction);
1823 }
1824 if (!trackListener.shouldScroll(direction)) {
1825 ((Timer)e.getSource()).stop();
1826 }
1827 }
1828 }
1829
1830 /**
1831 * Listener for resizing events.
1832 * <p>
1833 * This class should be treated as a "protected" inner class.
1834 * Instantiate it only within subclasses of <Foo>.
1835 */
1836 public class ComponentHandler extends ComponentAdapter {
1837 // NOTE: This class exists only for backward compatability. All
1838 // its functionality has been moved into Handler. If you need to add
1839 // new functionality add it to the Handler, but make sure this
1840 // class calls into the Handler.
1841 public void componentResized(ComponentEvent e) {
1842 getHandler().componentResized(e);
1843 }
1844 };
1845
1846 /**
1847 * Focus-change listener.
1848 * <p>
1849 * This class should be treated as a "protected" inner class.
1850 * Instantiate it only within subclasses of <Foo>.
1851 */
1852 public class FocusHandler implements FocusListener {
1853 // NOTE: This class exists only for backward compatability. All
1854 // its functionality has been moved into Handler. If you need to add
1855 // new functionality add it to the Handler, but make sure this
1856 // class calls into the Handler.
1857 public void focusGained(FocusEvent e) {
1858 getHandler().focusGained(e);
1859 }
1860
1861 public void focusLost(FocusEvent e) {
1862 getHandler().focusLost(e);
1863 }
1864 }
1865
1866 /**
1867 * As of Java 2 platform v1.3 this undocumented class is no longer used.
1868 * The recommended approach to creating bindings is to use a
1869 * combination of an <code>ActionMap</code>, to contain the action,
1870 * and an <code>InputMap</code> to contain the mapping from KeyStroke
1871 * to action description. The InputMap is is usually described in the
1872 * LookAndFeel tables.
1873 * <p>
1874 * Please refer to the key bindings specification for further details.
1875 * <p>
1876 * This class should be treated as a "protected" inner class.
1877 * Instantiate it only within subclasses of <Foo>.
1878 */
1879 public class ActionScroller extends AbstractAction {
1880 // NOTE: This class exists only for backward compatability. All
1881 // its functionality has been moved into Actions. If you need to add
1882 // new functionality add it to the Actions, but make sure this
1883 // class calls into the Actions.
1884 int dir;
1885 boolean block;
1886 JSlider slider;
1887
1888 public ActionScroller( JSlider slider, int dir, boolean block) {
1889 this.dir = dir;
1890 this.block = block;
1891 this.slider = slider;
1892 }
1893
1894 public void actionPerformed(ActionEvent e) {
1895 SHARED_ACTION.scroll(slider, BasicSliderUI.this, dir, block);
1896 }
1897
1898 public boolean isEnabled() {
1899 boolean b = true;
1900 if (slider != null) {
1901 b = slider.isEnabled();
1902 }
1903 return b;
1904 }
1905
1906 };
1907
1908
1909 /**
1910 * A static version of the above.
1911 */
1912 static class SharedActionScroller extends AbstractAction {
1913 // NOTE: This class exists only for backward compatability. All
1914 // its functionality has been moved into Actions. If you need to add
1915 // new functionality add it to the Actions, but make sure this
1916 // class calls into the Actions.
1917 int dir;
1918 boolean block;
1919
1920 public SharedActionScroller(int dir, boolean block) {
1921 this.dir = dir;
1922 this.block = block;
1923 }
1924
1925 public void actionPerformed(ActionEvent evt) {
1926 JSlider slider = (JSlider)evt.getSource();
1927 BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1928 slider.getUI(), BasicSliderUI.class);
1929 if (ui == null) {
1930 return;
1931 }
1932 SHARED_ACTION.scroll(slider, ui, dir, block);
1933 }
1934 }
1935
1936 private static class Actions extends UIAction {
1937 public static final String POSITIVE_UNIT_INCREMENT =
1938 "positiveUnitIncrement";
1939 public static final String POSITIVE_BLOCK_INCREMENT =
1940 "positiveBlockIncrement";
1941 public static final String NEGATIVE_UNIT_INCREMENT =
1942 "negativeUnitIncrement";
1943 public static final String NEGATIVE_BLOCK_INCREMENT =
1944 "negativeBlockIncrement";
1945 public static final String MIN_SCROLL_INCREMENT = "minScroll";
1946 public static final String MAX_SCROLL_INCREMENT = "maxScroll";
1947
1948
1949 Actions() {
1950 super(null);
1951 }
1952
1953 public Actions(String name) {
1954 super(name);
1955 }
1956
1957 public void actionPerformed(ActionEvent evt) {
1958 JSlider slider = (JSlider)evt.getSource();
1959 BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1960 slider.getUI(), BasicSliderUI.class);
1961 String name = getName();
1962
1963 if (ui == null) {
1964 return;
1965 }
1966 if (POSITIVE_UNIT_INCREMENT == name) {
1967 scroll(slider, ui, POSITIVE_SCROLL, false);
1968 } else if (NEGATIVE_UNIT_INCREMENT == name) {
1969 scroll(slider, ui, NEGATIVE_SCROLL, false);
1970 } else if (POSITIVE_BLOCK_INCREMENT == name) {
1971 scroll(slider, ui, POSITIVE_SCROLL, true);
1972 } else if (NEGATIVE_BLOCK_INCREMENT == name) {
1973 scroll(slider, ui, NEGATIVE_SCROLL, true);
1974 } else if (MIN_SCROLL_INCREMENT == name) {
1975 scroll(slider, ui, MIN_SCROLL, false);
1976 } else if (MAX_SCROLL_INCREMENT == name) {
1977 scroll(slider, ui, MAX_SCROLL, false);
1978 }
1979 }
1980
1981 private void scroll(JSlider slider, BasicSliderUI ui, int direction,
1982 boolean isBlock) {
1983 boolean invert = slider.getInverted();
1984
1985 if (direction == NEGATIVE_SCROLL || direction == POSITIVE_SCROLL) {
1986 if (invert) {
1987 direction = (direction == POSITIVE_SCROLL) ?
1988 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1989 }
1990
1991 if (isBlock) {
1992 ui.scrollByBlock(direction);
1993 } else {
1994 ui.scrollByUnit(direction);
1995 }
1996 } else { // MIN or MAX
1997 if (invert) {
1998 direction = (direction == MIN_SCROLL) ?
1999 MAX_SCROLL : MIN_SCROLL;
2000 }
2001
2002 slider.setValue((direction == MIN_SCROLL) ?
2003 slider.getMinimum() : slider.getMaximum());
2004 }
2005 }
2006 }
2007 }