blob: 70f5cba3b8344194c446e10e4b4d775bf2133310 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package javax.swing.plaf.basic;
27
28import java.awt.Component;
29import java.awt.Container;
30import java.awt.Adjustable;
31import java.awt.event.*;
32import java.awt.FontMetrics;
33import java.awt.Graphics;
34import java.awt.Dimension;
35import java.awt.Rectangle;
36import java.awt.Point;
37import java.awt.Insets;
38import java.awt.Color;
39import java.awt.IllegalComponentStateException;
40import java.awt.Polygon;
41import java.beans.*;
42import java.util.Dictionary;
43import java.util.Enumeration;
44
45import javax.swing.border.AbstractBorder;
46
47import javax.swing.*;
48import javax.swing.event.*;
49import javax.swing.plaf.*;
50import sun.swing.DefaultLookup;
51import sun.swing.UIAction;
52
53
54/**
55 * A Basic L&F implementation of SliderUI.
56 *
57 * @author Tom Santos
58 */
59public 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 protected void calculateThumbLocation() {
556 if ( slider.getSnapToTicks() ) {
557 int sliderValue = slider.getValue();
558 int snappedValue = sliderValue;
559 int majorTickSpacing = slider.getMajorTickSpacing();
560 int minorTickSpacing = slider.getMinorTickSpacing();
561 int tickSpacing = 0;
562
563 if ( minorTickSpacing > 0 ) {
564 tickSpacing = minorTickSpacing;
565 }
566 else if ( majorTickSpacing > 0 ) {
567 tickSpacing = majorTickSpacing;
568 }
569
570 if ( tickSpacing != 0 ) {
571 // If it's not on a tick, change the value
572 if ( (sliderValue - slider.getMinimum()) % tickSpacing != 0 ) {
573 float temp = (float)(sliderValue - slider.getMinimum()) / (float)tickSpacing;
574 int whichTick = Math.round( temp );
575
576 // This is the fix for the bug #6401380
577 if (temp - (int)temp == .5 && sliderValue < lastValue) {
578 whichTick --;
579 }
580 snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
581 }
582
583 if( snappedValue != sliderValue ) {
584 slider.setValue( snappedValue );
585 }
586 }
587 }
588
589 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
590 int valuePosition = xPositionForValue(slider.getValue());
591
592 thumbRect.x = valuePosition - (thumbRect.width / 2);
593 thumbRect.y = trackRect.y;
594 }
595 else {
596 int valuePosition = yPositionForValue(slider.getValue());
597
598 thumbRect.x = trackRect.x;
599 thumbRect.y = valuePosition - (thumbRect.height / 2);
600 }
601 }
602
603 protected void calculateTrackBuffer() {
604 if ( slider.getPaintLabels() && slider.getLabelTable() != null ) {
605 Component highLabel = getHighestValueLabel();
606 Component lowLabel = getLowestValueLabel();
607
608 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
609 trackBuffer = Math.max( highLabel.getBounds().width, lowLabel.getBounds().width ) / 2;
610 trackBuffer = Math.max( trackBuffer, thumbRect.width / 2 );
611 }
612 else {
613 trackBuffer = Math.max( highLabel.getBounds().height, lowLabel.getBounds().height ) / 2;
614 trackBuffer = Math.max( trackBuffer, thumbRect.height / 2 );
615 }
616 }
617 else {
618 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
619 trackBuffer = thumbRect.width / 2;
620 }
621 else {
622 trackBuffer = thumbRect.height / 2;
623 }
624 }
625 }
626
627
628 protected void calculateTrackRect() {
629 int centerSpacing = 0; // used to center sliders added using BorderLayout.CENTER (bug 4275631)
630 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
631 centerSpacing = thumbRect.height;
632 if ( slider.getPaintTicks() ) centerSpacing += getTickLength();
633 if ( slider.getPaintLabels() ) centerSpacing += getHeightOfTallestLabel();
634 trackRect.x = contentRect.x + trackBuffer;
635 trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1)/2;
636 trackRect.width = contentRect.width - (trackBuffer * 2);
637 trackRect.height = thumbRect.height;
638 }
639 else {
640 centerSpacing = thumbRect.width;
641 if (BasicGraphicsUtils.isLeftToRight(slider)) {
642 if ( slider.getPaintTicks() ) centerSpacing += getTickLength();
643 if ( slider.getPaintLabels() ) centerSpacing += getWidthOfWidestLabel();
644 } else {
645 if ( slider.getPaintTicks() ) centerSpacing -= getTickLength();
646 if ( slider.getPaintLabels() ) centerSpacing -= getWidthOfWidestLabel();
647 }
648 trackRect.x = contentRect.x + (contentRect.width - centerSpacing - 1)/2;
649 trackRect.y = contentRect.y + trackBuffer;
650 trackRect.width = thumbRect.width;
651 trackRect.height = contentRect.height - (trackBuffer * 2);
652 }
653
654 }
655
656 /**
657 * Gets the height of the tick area for horizontal sliders and the width of the
658 * tick area for vertical sliders. BasicSliderUI uses the returned value to
659 * determine the tick area rectangle. If you want to give your ticks some room,
660 * make this larger than you need and paint your ticks away from the sides in paintTicks().
661 */
662 protected int getTickLength() {
663 return 8;
664 }
665
666 protected void calculateTickRect() {
667 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
668 tickRect.x = trackRect.x;
669 tickRect.y = trackRect.y + trackRect.height;
670 tickRect.width = trackRect.width;
671 tickRect.height = (slider.getPaintTicks()) ? getTickLength() : 0;
672 }
673 else {
674 tickRect.width = (slider.getPaintTicks()) ? getTickLength() : 0;
675 if(BasicGraphicsUtils.isLeftToRight(slider)) {
676 tickRect.x = trackRect.x + trackRect.width;
677 }
678 else {
679 tickRect.x = trackRect.x - tickRect.width;
680 }
681 tickRect.y = trackRect.y;
682 tickRect.height = trackRect.height;
683 }
684 }
685
686 protected void calculateLabelRect() {
687 if ( slider.getPaintLabels() ) {
688 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
689 labelRect.x = tickRect.x - trackBuffer;
690 labelRect.y = tickRect.y + tickRect.height;
691 labelRect.width = tickRect.width + (trackBuffer * 2);
692 labelRect.height = getHeightOfTallestLabel();
693 }
694 else {
695 if(BasicGraphicsUtils.isLeftToRight(slider)) {
696 labelRect.x = tickRect.x + tickRect.width;
697 labelRect.width = getWidthOfWidestLabel();
698 }
699 else {
700 labelRect.width = getWidthOfWidestLabel();
701 labelRect.x = tickRect.x - labelRect.width;
702 }
703 labelRect.y = tickRect.y - trackBuffer;
704 labelRect.height = tickRect.height + (trackBuffer * 2);
705 }
706 }
707 else {
708 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
709 labelRect.x = tickRect.x;
710 labelRect.y = tickRect.y + tickRect.height;
711 labelRect.width = tickRect.width;
712 labelRect.height = 0;
713 }
714 else {
715 if(BasicGraphicsUtils.isLeftToRight(slider)) {
716 labelRect.x = tickRect.x + tickRect.width;
717 }
718 else {
719 labelRect.x = tickRect.x;
720 }
721 labelRect.y = tickRect.y;
722 labelRect.width = 0;
723 labelRect.height = tickRect.height;
724 }
725 }
726 }
727
728 protected Dimension getThumbSize() {
729 Dimension size = new Dimension();
730
731 if ( slider.getOrientation() == JSlider.VERTICAL ) {
732 size.width = 20;
733 size.height = 11;
734 }
735 else {
736 size.width = 11;
737 size.height = 20;
738 }
739
740 return size;
741 }
742
743 public class PropertyChangeHandler implements PropertyChangeListener {
744 // NOTE: This class exists only for backward compatability. All
745 // its functionality has been moved into Handler. If you need to add
746 // new functionality add it to the Handler, but make sure this
747 // class calls into the Handler.
748 public void propertyChange( PropertyChangeEvent e ) {
749 getHandler().propertyChange(e);
750 }
751 }
752
753 protected int getWidthOfWidestLabel() {
754 Dictionary dictionary = slider.getLabelTable();
755 int widest = 0;
756 if ( dictionary != null ) {
757 Enumeration keys = dictionary.keys();
758 while ( keys.hasMoreElements() ) {
759 Component label = (Component)dictionary.get( keys.nextElement() );
760 widest = Math.max( label.getPreferredSize().width, widest );
761 }
762 }
763 return widest;
764 }
765
766 protected int getHeightOfTallestLabel() {
767 Dictionary dictionary = slider.getLabelTable();
768 int tallest = 0;
769 if ( dictionary != null ) {
770 Enumeration keys = dictionary.keys();
771 while ( keys.hasMoreElements() ) {
772 Component label = (Component)dictionary.get( keys.nextElement() );
773 tallest = Math.max( label.getPreferredSize().height, tallest );
774 }
775 }
776 return tallest;
777 }
778
779 protected int getWidthOfHighValueLabel() {
780 Component label = getHighestValueLabel();
781 int width = 0;
782
783 if ( label != null ) {
784 width = label.getPreferredSize().width;
785 }
786
787 return width;
788 }
789
790 protected int getWidthOfLowValueLabel() {
791 Component label = getLowestValueLabel();
792 int width = 0;
793
794 if ( label != null ) {
795 width = label.getPreferredSize().width;
796 }
797
798 return width;
799 }
800
801 protected int getHeightOfHighValueLabel() {
802 Component label = getHighestValueLabel();
803 int height = 0;
804
805 if ( label != null ) {
806 height = label.getPreferredSize().height;
807 }
808
809 return height;
810 }
811
812 protected int getHeightOfLowValueLabel() {
813 Component label = getLowestValueLabel();
814 int height = 0;
815
816 if ( label != null ) {
817 height = label.getPreferredSize().height;
818 }
819
820 return height;
821 }
822
823 protected boolean drawInverted() {
824 if (slider.getOrientation()==JSlider.HORIZONTAL) {
825 if(BasicGraphicsUtils.isLeftToRight(slider)) {
826 return slider.getInverted();
827 } else {
828 return !slider.getInverted();
829 }
830 } else {
831 return slider.getInverted();
832 }
833 }
834
835 /**
836 * Returns the biggest value that has an entry in the label table.
837 *
838 * @return biggest value that has an entry in the label table, or
839 * null.
840 * @since 1.6
841 */
842 protected Integer getHighestValue() {
843 Dictionary dictionary = slider.getLabelTable();
844 if (dictionary != null) {
845 Enumeration keys = dictionary.keys();
846 int max = slider.getMinimum() - 1;
847 while (keys.hasMoreElements()) {
848 max = Math.max(max, ((Integer)keys.nextElement()).intValue());
849 }
850 if (max == slider.getMinimum() - 1) {
851 return null;
852 }
853 return max;
854 }
855 return null;
856 }
857
858 /**
859 * Returns the smallest value that has an entry in the label table.
860 *
861 * @return smallest value that has an entry in the label table, or
862 * null.
863 * @since 1.6
864 */
865 protected Integer getLowestValue() {
866 Dictionary dictionary = slider.getLabelTable();
867 if (dictionary != null) {
868 Enumeration keys = dictionary.keys();
869 int min = slider.getMaximum() + 1;
870 while (keys.hasMoreElements()) {
871 min = Math.min(min, ((Integer)keys.nextElement()).intValue());
872 }
873 if (min == slider.getMaximum() + 1) {
874 return null;
875 }
876 return min;
877 }
878 return null;
879 }
880
881
882 /**
883 * Returns the label that corresponds to the highest slider value in the label table.
884 * @see JSlider#setLabelTable
885 */
886 protected Component getLowestValueLabel() {
887 Integer min = getLowestValue();
888 if (min != null) {
889 return (Component)slider.getLabelTable().get(min);
890 }
891 return null;
892 }
893
894 /**
895 * Returns the label that corresponds to the lowest slider value in the label table.
896 * @see JSlider#setLabelTable
897 */
898 protected Component getHighestValueLabel() {
899 Integer max = getHighestValue();
900 if (max != null) {
901 return (Component)slider.getLabelTable().get(max);
902 }
903 return null;
904 }
905
906 public void paint( Graphics g, JComponent c ) {
907 recalculateIfInsetsChanged();
908 recalculateIfOrientationChanged();
909 Rectangle clip = g.getClipBounds();
910
911 if ( !clip.intersects(trackRect) && slider.getPaintTrack())
912 calculateGeometry();
913
914 if ( slider.getPaintTrack() && clip.intersects( trackRect ) ) {
915 paintTrack( g );
916 }
917 if ( slider.getPaintTicks() && clip.intersects( tickRect ) ) {
918 paintTicks( g );
919 }
920 if ( slider.getPaintLabels() && clip.intersects( labelRect ) ) {
921 paintLabels( g );
922 }
923 if ( slider.hasFocus() && clip.intersects( focusRect ) ) {
924 paintFocus( g );
925 }
926 if ( clip.intersects( thumbRect ) ) {
927 paintThumb( g );
928 }
929 }
930
931 protected void recalculateIfInsetsChanged() {
932 Insets newInsets = slider.getInsets();
933 if ( !newInsets.equals( insetCache ) ) {
934 insetCache = newInsets;
935 calculateGeometry();
936 }
937 }
938
939 protected void recalculateIfOrientationChanged() {
940 boolean ltr = BasicGraphicsUtils.isLeftToRight(slider);
941 if ( ltr!=leftToRightCache ) {
942 leftToRightCache = ltr;
943 calculateGeometry();
944 }
945 }
946
947 public void paintFocus(Graphics g) {
948 g.setColor( getFocusColor() );
949
950 BasicGraphicsUtils.drawDashedRect( g, focusRect.x, focusRect.y,
951 focusRect.width, focusRect.height );
952 }
953
954 public void paintTrack(Graphics g) {
955
956 Rectangle trackBounds = trackRect;
957
958 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
959 int cy = (trackBounds.height / 2) - 2;
960 int cw = trackBounds.width;
961
962 g.translate(trackBounds.x, trackBounds.y + cy);
963
964 g.setColor(getShadowColor());
965 g.drawLine(0, 0, cw - 1, 0);
966 g.drawLine(0, 1, 0, 2);
967 g.setColor(getHighlightColor());
968 g.drawLine(0, 3, cw, 3);
969 g.drawLine(cw, 0, cw, 3);
970 g.setColor(Color.black);
971 g.drawLine(1, 1, cw-2, 1);
972
973 g.translate(-trackBounds.x, -(trackBounds.y + cy));
974 }
975 else {
976 int cx = (trackBounds.width / 2) - 2;
977 int ch = trackBounds.height;
978
979 g.translate(trackBounds.x + cx, trackBounds.y);
980
981 g.setColor(getShadowColor());
982 g.drawLine(0, 0, 0, ch - 1);
983 g.drawLine(1, 0, 2, 0);
984 g.setColor(getHighlightColor());
985 g.drawLine(3, 0, 3, ch);
986 g.drawLine(0, ch, 3, ch);
987 g.setColor(Color.black);
988 g.drawLine(1, 1, 1, ch-2);
989
990 g.translate(-(trackBounds.x + cx), -trackBounds.y);
991 }
992 }
993
994 public void paintTicks(Graphics g) {
995 Rectangle tickBounds = tickRect;
996 int i;
997 int maj, min, max;
998 int w = tickBounds.width;
999 int h = tickBounds.height;
1000 int centerEffect, tickHeight;
1001
1002 g.setColor(DefaultLookup.getColor(slider, this, "Slider.tickColor", Color.black));
1003
1004 maj = slider.getMajorTickSpacing();
1005 min = slider.getMinorTickSpacing();
1006
1007 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1008 g.translate( 0, tickBounds.y);
1009
1010 int value = slider.getMinimum();
1011 int xPos = 0;
1012
1013 if ( slider.getMinorTickSpacing() > 0 ) {
1014 while ( value <= slider.getMaximum() ) {
1015 xPos = xPositionForValue( value );
1016 paintMinorTickForHorizSlider( g, tickBounds, xPos );
1017 value += slider.getMinorTickSpacing();
1018 }
1019 }
1020
1021 if ( slider.getMajorTickSpacing() > 0 ) {
1022 value = slider.getMinimum();
1023
1024 while ( value <= slider.getMaximum() ) {
1025 xPos = xPositionForValue( value );
1026 paintMajorTickForHorizSlider( g, tickBounds, xPos );
1027 value += slider.getMajorTickSpacing();
1028 }
1029 }
1030
1031 g.translate( 0, -tickBounds.y);
1032 }
1033 else {
1034 g.translate(tickBounds.x, 0);
1035
1036 int value = slider.getMinimum();
1037 int yPos = 0;
1038
1039 if ( slider.getMinorTickSpacing() > 0 ) {
1040 int offset = 0;
1041 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1042 offset = tickBounds.width - tickBounds.width / 2;
1043 g.translate(offset, 0);
1044 }
1045
1046 while ( value <= slider.getMaximum() ) {
1047 yPos = yPositionForValue( value );
1048 paintMinorTickForVertSlider( g, tickBounds, yPos );
1049 value += slider.getMinorTickSpacing();
1050 }
1051
1052 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1053 g.translate(-offset, 0);
1054 }
1055 }
1056
1057 if ( slider.getMajorTickSpacing() > 0 ) {
1058 value = slider.getMinimum();
1059 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1060 g.translate(2, 0);
1061 }
1062
1063 while ( value <= slider.getMaximum() ) {
1064 yPos = yPositionForValue( value );
1065 paintMajorTickForVertSlider( g, tickBounds, yPos );
1066 value += slider.getMajorTickSpacing();
1067 }
1068
1069 if(!BasicGraphicsUtils.isLeftToRight(slider)) {
1070 g.translate(-2, 0);
1071 }
1072 }
1073 g.translate(-tickBounds.x, 0);
1074 }
1075 }
1076
1077 protected void paintMinorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1078 g.drawLine( x, 0, x, tickBounds.height / 2 - 1 );
1079 }
1080
1081 protected void paintMajorTickForHorizSlider( Graphics g, Rectangle tickBounds, int x ) {
1082 g.drawLine( x, 0, x, tickBounds.height - 2 );
1083 }
1084
1085 protected void paintMinorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1086 g.drawLine( 0, y, tickBounds.width / 2 - 1, y );
1087 }
1088
1089 protected void paintMajorTickForVertSlider( Graphics g, Rectangle tickBounds, int y ) {
1090 g.drawLine( 0, y, tickBounds.width - 2, y );
1091 }
1092
1093 public void paintLabels( Graphics g ) {
1094 Rectangle labelBounds = labelRect;
1095
1096 Dictionary dictionary = slider.getLabelTable();
1097 if ( dictionary != null ) {
1098 Enumeration keys = dictionary.keys();
1099 int minValue = slider.getMinimum();
1100 int maxValue = slider.getMaximum();
1101 boolean enabled = slider.isEnabled();
1102 while ( keys.hasMoreElements() ) {
1103 Integer key = (Integer)keys.nextElement();
1104 int value = key.intValue();
1105 if (value >= minValue && value <= maxValue) {
1106 Component label = (Component)dictionary.get( key );
1107 if (label instanceof JComponent) {
1108 ((JComponent)label).setEnabled(enabled);
1109 }
1110 if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1111 g.translate( 0, labelBounds.y );
1112 paintHorizontalLabel( g, value, label );
1113 g.translate( 0, -labelBounds.y );
1114 }
1115 else {
1116 int offset = 0;
1117 if (!BasicGraphicsUtils.isLeftToRight(slider)) {
1118 offset = labelBounds.width -
1119 label.getPreferredSize().width;
1120 }
1121 g.translate( labelBounds.x + offset, 0 );
1122 paintVerticalLabel( g, value, label );
1123 g.translate( -labelBounds.x - offset, 0 );
1124 }
1125 }
1126 }
1127 }
1128
1129 }
1130
1131 /**
1132 * Called for every label in the label table. Used to draw the labels for horizontal sliders.
1133 * The graphics have been translated to labelRect.y already.
1134 * @see JSlider#setLabelTable
1135 */
1136 protected void paintHorizontalLabel( Graphics g, int value, Component label ) {
1137 int labelCenter = xPositionForValue( value );
1138 int labelLeft = labelCenter - (label.getPreferredSize().width / 2);
1139 g.translate( labelLeft, 0 );
1140 label.paint( g );
1141 g.translate( -labelLeft, 0 );
1142 }
1143
1144 /**
1145 * Called for every label in the label table. Used to draw the labels for vertical sliders.
1146 * The graphics have been translated to labelRect.x already.
1147 * @see JSlider#setLabelTable
1148 */
1149 protected void paintVerticalLabel( Graphics g, int value, Component label ) {
1150 int labelCenter = yPositionForValue( value );
1151 int labelTop = labelCenter - (label.getPreferredSize().height / 2);
1152 g.translate( 0, labelTop );
1153 label.paint( g );
1154 g.translate( 0, -labelTop );
1155 }
1156
1157 public void paintThumb(Graphics g) {
1158 Rectangle knobBounds = thumbRect;
1159 int w = knobBounds.width;
1160 int h = knobBounds.height;
1161
1162 g.translate(knobBounds.x, knobBounds.y);
1163
1164 if ( slider.isEnabled() ) {
1165 g.setColor(slider.getBackground());
1166 }
1167 else {
1168 g.setColor(slider.getBackground().darker());
1169 }
1170
1171 Boolean paintThumbArrowShape =
1172 (Boolean)slider.getClientProperty("Slider.paintThumbArrowShape");
1173
1174 if ((!slider.getPaintTicks() && paintThumbArrowShape == null) ||
1175 paintThumbArrowShape == Boolean.FALSE) {
1176
1177 // "plain" version
1178 g.fillRect(0, 0, w, h);
1179
1180 g.setColor(Color.black);
1181 g.drawLine(0, h-1, w-1, h-1);
1182 g.drawLine(w-1, 0, w-1, h-1);
1183
1184 g.setColor(highlightColor);
1185 g.drawLine(0, 0, 0, h-2);
1186 g.drawLine(1, 0, w-2, 0);
1187
1188 g.setColor(shadowColor);
1189 g.drawLine(1, h-2, w-2, h-2);
1190 g.drawLine(w-2, 1, w-2, h-3);
1191 }
1192 else if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
1193 int cw = w / 2;
1194 g.fillRect(1, 1, w-3, h-1-cw);
1195 Polygon p = new Polygon();
1196 p.addPoint(1, h-cw);
1197 p.addPoint(cw-1, h-1);
1198 p.addPoint(w-2, h-1-cw);
1199 g.fillPolygon(p);
1200
1201 g.setColor(highlightColor);
1202 g.drawLine(0, 0, w-2, 0);
1203 g.drawLine(0, 1, 0, h-1-cw);
1204 g.drawLine(0, h-cw, cw-1, h-1);
1205
1206 g.setColor(Color.black);
1207 g.drawLine(w-1, 0, w-1, h-2-cw);
1208 g.drawLine(w-1, h-1-cw, w-1-cw, h-1);
1209
1210 g.setColor(shadowColor);
1211 g.drawLine(w-2, 1, w-2, h-2-cw);
1212 g.drawLine(w-2, h-1-cw, w-1-cw, h-2);
1213 }
1214 else { // vertical
1215 int cw = h / 2;
1216 if(BasicGraphicsUtils.isLeftToRight(slider)) {
1217 g.fillRect(1, 1, w-1-cw, h-3);
1218 Polygon p = new Polygon();
1219 p.addPoint(w-cw-1, 0);
1220 p.addPoint(w-1, cw);
1221 p.addPoint(w-1-cw, h-2);
1222 g.fillPolygon(p);
1223
1224 g.setColor(highlightColor);
1225 g.drawLine(0, 0, 0, h - 2); // left
1226 g.drawLine(1, 0, w-1-cw, 0); // top
1227 g.drawLine(w-cw-1, 0, w-1, cw); // top slant
1228
1229 g.setColor(Color.black);
1230 g.drawLine(0, h-1, w-2-cw, h-1); // bottom
1231 g.drawLine(w-1-cw, h-1, w-1, h-1-cw); // bottom slant
1232
1233 g.setColor(shadowColor);
1234 g.drawLine(1, h-2, w-2-cw, h-2 ); // bottom
1235 g.drawLine(w-1-cw, h-2, w-2, h-cw-1 ); // bottom slant
1236 }
1237 else {
1238 g.fillRect(5, 1, w-1-cw, h-3);
1239 Polygon p = new Polygon();
1240 p.addPoint(cw, 0);
1241 p.addPoint(0, cw);
1242 p.addPoint(cw, h-2);
1243 g.fillPolygon(p);
1244
1245 g.setColor(highlightColor);
1246 g.drawLine(cw-1, 0, w-2, 0); // top
1247 g.drawLine(0, cw, cw, 0); // top slant
1248
1249 g.setColor(Color.black);
1250 g.drawLine(0, h-1-cw, cw, h-1 ); // bottom slant
1251 g.drawLine(cw, h-1, w-1, h-1); // bottom
1252
1253 g.setColor(shadowColor);
1254 g.drawLine(cw, h-2, w-2, h-2 ); // bottom
1255 g.drawLine(w-1, 1, w-1, h-2 ); // right
1256 }
1257 }
1258
1259 g.translate(-knobBounds.x, -knobBounds.y);
1260 }
1261
1262 // Used exclusively by setThumbLocation()
1263 private static Rectangle unionRect = new Rectangle();
1264
1265 public void setThumbLocation(int x, int y) {
1266 unionRect.setBounds( thumbRect );
1267
1268 thumbRect.setLocation( x, y );
1269
1270 SwingUtilities.computeUnion( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, unionRect );
1271 slider.repaint( unionRect.x, unionRect.y, unionRect.width, unionRect.height );
1272 }
1273
1274 public void scrollByBlock(int direction) {
1275 synchronized(slider) {
1276
1277 int oldValue = slider.getValue();
1278 int blockIncrement =
1279 (slider.getMaximum() - slider.getMinimum()) / 10;
1280 if (blockIncrement <= 0 &&
1281 slider.getMaximum() > slider.getMinimum()) {
1282
1283 blockIncrement = 1;
1284 }
1285
1286 int delta = blockIncrement * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1287 slider.setValue(oldValue + delta);
1288 }
1289 }
1290
1291 public void scrollByUnit(int direction) {
1292 synchronized(slider) {
1293
1294 int oldValue = slider.getValue();
1295 int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
1296
1297 slider.setValue(oldValue + delta);
1298 }
1299 }
1300
1301 /**
1302 * This function is called when a mousePressed was detected in the track, not
1303 * in the thumb. The default behavior is to scroll by block. You can
1304 * override this method to stop it from scrolling or to add additional behavior.
1305 */
1306 protected void scrollDueToClickInTrack( int dir ) {
1307 scrollByBlock( dir );
1308 }
1309
1310 protected int xPositionForValue( int value ) {
1311 int min = slider.getMinimum();
1312 int max = slider.getMaximum();
1313 int trackLength = trackRect.width;
1314 double valueRange = (double)max - (double)min;
1315 double pixelsPerValue = (double)trackLength / valueRange;
1316 int trackLeft = trackRect.x;
1317 int trackRight = trackRect.x + (trackRect.width - 1);
1318 int xPosition;
1319
1320 if ( !drawInverted() ) {
1321 xPosition = trackLeft;
1322 xPosition += Math.round( pixelsPerValue * ((double)value - min) );
1323 }
1324 else {
1325 xPosition = trackRight;
1326 xPosition -= Math.round( pixelsPerValue * ((double)value - min) );
1327 }
1328
1329 xPosition = Math.max( trackLeft, xPosition );
1330 xPosition = Math.min( trackRight, xPosition );
1331
1332 return xPosition;
1333 }
1334
1335 protected int yPositionForValue( int value ) {
1336 return yPositionForValue(value, trackRect.y, trackRect.height);
1337 }
1338
1339 /**
1340 * Returns the y location for the specified value. No checking is
1341 * done on the arguments. In particular if <code>trackHeight</code> is
1342 * negative undefined results may occur.
1343 *
1344 * @param value the slider value to get the location for
1345 * @param trackY y-origin of the track
1346 * @param trackHeight the height of the track
1347 * @since 1.6
1348 */
1349 protected int yPositionForValue(int value, int trackY, int trackHeight) {
1350 int min = slider.getMinimum();
1351 int max = slider.getMaximum();
1352 double valueRange = (double)max - (double)min;
1353 double pixelsPerValue = (double)trackHeight / (double)valueRange;
1354 int trackBottom = trackY + (trackHeight - 1);
1355 int yPosition;
1356
1357 if ( !drawInverted() ) {
1358 yPosition = trackY;
1359 yPosition += Math.round( pixelsPerValue * ((double)max - value ) );
1360 }
1361 else {
1362 yPosition = trackY;
1363 yPosition += Math.round( pixelsPerValue * ((double)value - min) );
1364 }
1365
1366 yPosition = Math.max( trackY, yPosition );
1367 yPosition = Math.min( trackBottom, yPosition );
1368
1369 return yPosition;
1370 }
1371
1372 /**
1373 * Returns a value give a y position. If yPos is past the track at the top or the
1374 * bottom it will set the value to the min or max of the slider, depending if the
1375 * slider is inverted or not.
1376 */
1377 public int valueForYPosition( int yPos ) {
1378 int value;
1379 final int minValue = slider.getMinimum();
1380 final int maxValue = slider.getMaximum();
1381 final int trackLength = trackRect.height;
1382 final int trackTop = trackRect.y;
1383 final int trackBottom = trackRect.y + (trackRect.height - 1);
1384
1385 if ( yPos <= trackTop ) {
1386 value = drawInverted() ? minValue : maxValue;
1387 }
1388 else if ( yPos >= trackBottom ) {
1389 value = drawInverted() ? maxValue : minValue;
1390 }
1391 else {
1392 int distanceFromTrackTop = yPos - trackTop;
1393 double valueRange = (double)maxValue - (double)minValue;
1394 double valuePerPixel = valueRange / (double)trackLength;
1395 int valueFromTrackTop = (int)Math.round( distanceFromTrackTop * valuePerPixel );
1396
1397 value = drawInverted() ? minValue + valueFromTrackTop : maxValue - valueFromTrackTop;
1398 }
1399
1400 return value;
1401 }
1402
1403 /**
1404 * Returns a value give an x position. If xPos is past the track at the left or the
1405 * right it will set the value to the min or max of the slider, depending if the
1406 * slider is inverted or not.
1407 */
1408 public int valueForXPosition( int xPos ) {
1409 int value;
1410 final int minValue = slider.getMinimum();
1411 final int maxValue = slider.getMaximum();
1412 final int trackLength = trackRect.width;
1413 final int trackLeft = trackRect.x;
1414 final int trackRight = trackRect.x + (trackRect.width - 1);
1415
1416 if ( xPos <= trackLeft ) {
1417 value = drawInverted() ? maxValue : minValue;
1418 }
1419 else if ( xPos >= trackRight ) {
1420 value = drawInverted() ? minValue : maxValue;
1421 }
1422 else {
1423 int distanceFromTrackLeft = xPos - trackLeft;
1424 double valueRange = (double)maxValue - (double)minValue;
1425 double valuePerPixel = valueRange / (double)trackLength;
1426 int valueFromTrackLeft = (int)Math.round( distanceFromTrackLeft * valuePerPixel );
1427
1428 value = drawInverted() ? maxValue - valueFromTrackLeft :
1429 minValue + valueFromTrackLeft;
1430 }
1431
1432 return value;
1433 }
1434
1435
1436 private class Handler implements ChangeListener,
1437 ComponentListener, FocusListener, PropertyChangeListener {
1438 // Change Handler
1439 public void stateChanged(ChangeEvent e) {
1440 if (!isDragging) {
1441 calculateThumbLocation();
1442 slider.repaint();
1443 }
1444 lastValue = slider.getValue();
1445 }
1446
1447 // Component Handler
1448 public void componentHidden(ComponentEvent e) { }
1449 public void componentMoved(ComponentEvent e) { }
1450 public void componentResized(ComponentEvent e) {
1451 calculateGeometry();
1452 slider.repaint();
1453 }
1454 public void componentShown(ComponentEvent e) { }
1455
1456 // Focus Handler
1457 public void focusGained(FocusEvent e) { slider.repaint(); }
1458 public void focusLost(FocusEvent e) { slider.repaint(); }
1459
1460 // Property Change Handler
1461 public void propertyChange(PropertyChangeEvent e) {
1462 String propertyName = e.getPropertyName();
1463 if (propertyName == "orientation" ||
1464 propertyName == "inverted" ||
1465 propertyName == "labelTable" ||
1466 propertyName == "majorTickSpacing" ||
1467 propertyName == "minorTickSpacing" ||
1468 propertyName == "paintTicks" ||
1469 propertyName == "paintTrack" ||
1470 propertyName == "font" ||
1471 propertyName == "paintLabels") {
1472 checkedLabelBaselines = false;
1473 calculateGeometry();
1474 slider.repaint();
1475 } else if (propertyName == "componentOrientation") {
1476 calculateGeometry();
1477 slider.repaint();
1478 InputMap km = getInputMap(JComponent.WHEN_FOCUSED, slider);
1479 SwingUtilities.replaceUIInputMap(slider,
1480 JComponent.WHEN_FOCUSED, km);
1481 } else if (propertyName == "model") {
1482 ((BoundedRangeModel)e.getOldValue()).removeChangeListener(
1483 changeListener);
1484 ((BoundedRangeModel)e.getNewValue()).addChangeListener(
1485 changeListener);
1486 calculateThumbLocation();
1487 slider.repaint();
1488 }
1489 }
1490 }
1491
1492 /////////////////////////////////////////////////////////////////////////
1493 /// Model Listener Class
1494 /////////////////////////////////////////////////////////////////////////
1495 /**
1496 * Data model listener.
1497 *
1498 * This class should be treated as a &quot;protected&quot; inner class.
1499 * Instantiate it only within subclasses of <Foo>.
1500 */
1501 public class ChangeHandler implements ChangeListener {
1502 // NOTE: This class exists only for backward compatability. All
1503 // its functionality has been moved into Handler. If you need to add
1504 // new functionality add it to the Handler, but make sure this
1505 // class calls into the Handler.
1506 public void stateChanged(ChangeEvent e) {
1507 getHandler().stateChanged(e);
1508 }
1509 }
1510
1511 /////////////////////////////////////////////////////////////////////////
1512 /// Track Listener Class
1513 /////////////////////////////////////////////////////////////////////////
1514 /**
1515 * Track mouse movements.
1516 *
1517 * This class should be treated as a &quot;protected&quot; inner class.
1518 * Instantiate it only within subclasses of <Foo>.
1519 */
1520 public class TrackListener extends MouseInputAdapter {
1521 protected transient int offset;
1522 protected transient int currentMouseX, currentMouseY;
1523
1524 public void mouseReleased(MouseEvent e) {
1525 if (!slider.isEnabled()) {
1526 return;
1527 }
1528
1529 offset = 0;
1530 scrollTimer.stop();
1531
1532 // This is the way we have to determine snap-to-ticks. It's
1533 // hard to explain but since ChangeEvents don't give us any
1534 // idea what has changed we don't have a way to stop the thumb
1535 // bounds from being recalculated. Recalculating the thumb
1536 // bounds moves the thumb over the current value (i.e., snapping
1537 // to the ticks).
1538 if (slider.getSnapToTicks() /*|| slider.getSnapToValue()*/ ) {
1539 isDragging = false;
1540 slider.setValueIsAdjusting(false);
1541 }
1542 else {
1543 slider.setValueIsAdjusting(false);
1544 isDragging = false;
1545 }
1546 slider.repaint();
1547 }
1548
1549 /**
1550 * If the mouse is pressed above the "thumb" component
1551 * then reduce the scrollbars value by one page ("page up"),
1552 * otherwise increase it by one page. If there is no
1553 * thumb then page up if the mouse is in the upper half
1554 * of the track.
1555 */
1556 public void mousePressed(MouseEvent e) {
1557 if (!slider.isEnabled()) {
1558 return;
1559 }
1560
1561 // We should recalculate geometry just before
1562 // calculation of the thumb movement direction.
1563 // It is important for the case, when JSlider
1564 // is a cell editor in JTable. See 6348946.
1565 calculateGeometry();
1566
1567 currentMouseX = e.getX();
1568 currentMouseY = e.getY();
1569
1570 if (slider.isRequestFocusEnabled()) {
1571 slider.requestFocus();
1572 }
1573
1574 // Clicked in the Thumb area?
1575 if (thumbRect.contains(currentMouseX, currentMouseY)) {
1576 switch (slider.getOrientation()) {
1577 case JSlider.VERTICAL:
1578 offset = currentMouseY - thumbRect.y;
1579 break;
1580 case JSlider.HORIZONTAL:
1581 offset = currentMouseX - thumbRect.x;
1582 break;
1583 }
1584 isDragging = true;
1585 return;
1586 }
1587 isDragging = false;
1588 slider.setValueIsAdjusting(true);
1589
1590 Dimension sbSize = slider.getSize();
1591 int direction = POSITIVE_SCROLL;
1592
1593 switch (slider.getOrientation()) {
1594 case JSlider.VERTICAL:
1595 if ( thumbRect.isEmpty() ) {
1596 int scrollbarCenter = sbSize.height / 2;
1597 if ( !drawInverted() ) {
1598 direction = (currentMouseY < scrollbarCenter) ?
1599 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1600 }
1601 else {
1602 direction = (currentMouseY < scrollbarCenter) ?
1603 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1604 }
1605 }
1606 else {
1607 int thumbY = thumbRect.y;
1608 if ( !drawInverted() ) {
1609 direction = (currentMouseY < thumbY) ?
1610 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1611 }
1612 else {
1613 direction = (currentMouseY < thumbY) ?
1614 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1615 }
1616 }
1617 break;
1618 case JSlider.HORIZONTAL:
1619 if ( thumbRect.isEmpty() ) {
1620 int scrollbarCenter = sbSize.width / 2;
1621 if ( !drawInverted() ) {
1622 direction = (currentMouseX < scrollbarCenter) ?
1623 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1624 }
1625 else {
1626 direction = (currentMouseX < scrollbarCenter) ?
1627 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1628 }
1629 }
1630 else {
1631 int thumbX = thumbRect.x;
1632 if ( !drawInverted() ) {
1633 direction = (currentMouseX < thumbX) ?
1634 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1635 }
1636 else {
1637 direction = (currentMouseX < thumbX) ?
1638 POSITIVE_SCROLL : NEGATIVE_SCROLL;
1639 }
1640 }
1641 break;
1642 }
1643
1644 if (shouldScroll(direction)) {
1645 scrollDueToClickInTrack(direction);
1646 }
1647 if (shouldScroll(direction)) {
1648 scrollTimer.stop();
1649 scrollListener.setDirection(direction);
1650 scrollTimer.start();
1651 }
1652 }
1653
1654 public boolean shouldScroll(int direction) {
1655 Rectangle r = thumbRect;
1656 if (slider.getOrientation() == JSlider.VERTICAL) {
1657 if (drawInverted() ? direction < 0 : direction > 0) {
1658 if (r.y <= currentMouseY) {
1659 return false;
1660 }
1661 }
1662 else if (r.y + r.height >= currentMouseY) {
1663 return false;
1664 }
1665 }
1666 else {
1667 if (drawInverted() ? direction < 0 : direction > 0) {
1668 if (r.x + r.width >= currentMouseX) {
1669 return false;
1670 }
1671 }
1672 else if (r.x <= currentMouseX) {
1673 return false;
1674 }
1675 }
1676
1677 if (direction > 0 && slider.getValue() + slider.getExtent() >=
1678 slider.getMaximum()) {
1679 return false;
1680 }
1681 else if (direction < 0 && slider.getValue() <=
1682 slider.getMinimum()) {
1683 return false;
1684 }
1685
1686 return true;
1687 }
1688
1689 /**
1690 * Set the models value to the position of the top/left
1691 * of the thumb relative to the origin of the track.
1692 */
1693 public void mouseDragged(MouseEvent e) {
1694 int thumbMiddle = 0;
1695
1696 if (!slider.isEnabled()) {
1697 return;
1698 }
1699
1700 currentMouseX = e.getX();
1701 currentMouseY = e.getY();
1702
1703 if (!isDragging) {
1704 return;
1705 }
1706
1707 slider.setValueIsAdjusting(true);
1708
1709 switch (slider.getOrientation()) {
1710 case JSlider.VERTICAL:
1711 int halfThumbHeight = thumbRect.height / 2;
1712 int thumbTop = e.getY() - offset;
1713 int trackTop = trackRect.y;
1714 int trackBottom = trackRect.y + (trackRect.height - 1);
1715 int vMax = yPositionForValue(slider.getMaximum() -
1716 slider.getExtent());
1717
1718 if (drawInverted()) {
1719 trackBottom = vMax;
1720 }
1721 else {
1722 trackTop = vMax;
1723 }
1724 thumbTop = Math.max(thumbTop, trackTop - halfThumbHeight);
1725 thumbTop = Math.min(thumbTop, trackBottom - halfThumbHeight);
1726
1727 setThumbLocation(thumbRect.x, thumbTop);
1728
1729 thumbMiddle = thumbTop + halfThumbHeight;
1730 slider.setValue( valueForYPosition( thumbMiddle ) );
1731 break;
1732 case JSlider.HORIZONTAL:
1733 int halfThumbWidth = thumbRect.width / 2;
1734 int thumbLeft = e.getX() - offset;
1735 int trackLeft = trackRect.x;
1736 int trackRight = trackRect.x + (trackRect.width - 1);
1737 int hMax = xPositionForValue(slider.getMaximum() -
1738 slider.getExtent());
1739
1740 if (drawInverted()) {
1741 trackLeft = hMax;
1742 }
1743 else {
1744 trackRight = hMax;
1745 }
1746 thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
1747 thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
1748
1749 setThumbLocation(thumbLeft, thumbRect.y);
1750
1751 thumbMiddle = thumbLeft + halfThumbWidth;
1752 slider.setValue(valueForXPosition(thumbMiddle));
1753 break;
1754 default:
1755 return;
1756 }
1757 }
1758
1759 public void mouseMoved(MouseEvent e) { }
1760 }
1761
1762 /**
1763 * Scroll-event listener.
1764 *
1765 * This class should be treated as a &quot;protected&quot; inner class.
1766 * Instantiate it only within subclasses of <Foo>.
1767 */
1768 public class ScrollListener implements ActionListener {
1769 // changed this class to public to avoid bogus IllegalAccessException
1770 // bug in InternetExplorer browser. It was protected. Work around
1771 // for 4109432
1772 int direction = POSITIVE_SCROLL;
1773 boolean useBlockIncrement;
1774
1775 public ScrollListener() {
1776 direction = POSITIVE_SCROLL;
1777 useBlockIncrement = true;
1778 }
1779
1780 public ScrollListener(int dir, boolean block) {
1781 direction = dir;
1782 useBlockIncrement = block;
1783 }
1784
1785 public void setDirection(int direction) {
1786 this.direction = direction;
1787 }
1788
1789 public void setScrollByBlock(boolean block) {
1790 this.useBlockIncrement = block;
1791 }
1792
1793 public void actionPerformed(ActionEvent e) {
1794 if (useBlockIncrement) {
1795 scrollByBlock(direction);
1796 }
1797 else {
1798 scrollByUnit(direction);
1799 }
1800 if (!trackListener.shouldScroll(direction)) {
1801 ((Timer)e.getSource()).stop();
1802 }
1803 }
1804 }
1805
1806 /**
1807 * Listener for resizing events.
1808 * <p>
1809 * This class should be treated as a &quot;protected&quot; inner class.
1810 * Instantiate it only within subclasses of <Foo>.
1811 */
1812 public class ComponentHandler extends ComponentAdapter {
1813 // NOTE: This class exists only for backward compatability. All
1814 // its functionality has been moved into Handler. If you need to add
1815 // new functionality add it to the Handler, but make sure this
1816 // class calls into the Handler.
1817 public void componentResized(ComponentEvent e) {
1818 getHandler().componentResized(e);
1819 }
1820 };
1821
1822 /**
1823 * Focus-change listener.
1824 * <p>
1825 * This class should be treated as a &quot;protected&quot; inner class.
1826 * Instantiate it only within subclasses of <Foo>.
1827 */
1828 public class FocusHandler implements FocusListener {
1829 // NOTE: This class exists only for backward compatability. All
1830 // its functionality has been moved into Handler. If you need to add
1831 // new functionality add it to the Handler, but make sure this
1832 // class calls into the Handler.
1833 public void focusGained(FocusEvent e) {
1834 getHandler().focusGained(e);
1835 }
1836
1837 public void focusLost(FocusEvent e) {
1838 getHandler().focusLost(e);
1839 }
1840 }
1841
1842 /**
1843 * As of Java 2 platform v1.3 this undocumented class is no longer used.
1844 * The recommended approach to creating bindings is to use a
1845 * combination of an <code>ActionMap</code>, to contain the action,
1846 * and an <code>InputMap</code> to contain the mapping from KeyStroke
1847 * to action description. The InputMap is is usually described in the
1848 * LookAndFeel tables.
1849 * <p>
1850 * Please refer to the key bindings specification for further details.
1851 * <p>
1852 * This class should be treated as a &quot;protected&quot; inner class.
1853 * Instantiate it only within subclasses of <Foo>.
1854 */
1855 public class ActionScroller extends AbstractAction {
1856 // NOTE: This class exists only for backward compatability. All
1857 // its functionality has been moved into Actions. If you need to add
1858 // new functionality add it to the Actions, but make sure this
1859 // class calls into the Actions.
1860 int dir;
1861 boolean block;
1862 JSlider slider;
1863
1864 public ActionScroller( JSlider slider, int dir, boolean block) {
1865 this.dir = dir;
1866 this.block = block;
1867 this.slider = slider;
1868 }
1869
1870 public void actionPerformed(ActionEvent e) {
1871 SHARED_ACTION.scroll(slider, BasicSliderUI.this, dir, block);
1872 }
1873
1874 public boolean isEnabled() {
1875 boolean b = true;
1876 if (slider != null) {
1877 b = slider.isEnabled();
1878 }
1879 return b;
1880 }
1881
1882 };
1883
1884
1885 /**
1886 * A static version of the above.
1887 */
1888 static class SharedActionScroller extends AbstractAction {
1889 // NOTE: This class exists only for backward compatability. All
1890 // its functionality has been moved into Actions. If you need to add
1891 // new functionality add it to the Actions, but make sure this
1892 // class calls into the Actions.
1893 int dir;
1894 boolean block;
1895
1896 public SharedActionScroller(int dir, boolean block) {
1897 this.dir = dir;
1898 this.block = block;
1899 }
1900
1901 public void actionPerformed(ActionEvent evt) {
1902 JSlider slider = (JSlider)evt.getSource();
1903 BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1904 slider.getUI(), BasicSliderUI.class);
1905 if (ui == null) {
1906 return;
1907 }
1908 SHARED_ACTION.scroll(slider, ui, dir, block);
1909 }
1910 }
1911
1912 private static class Actions extends UIAction {
1913 public static final String POSITIVE_UNIT_INCREMENT =
1914 "positiveUnitIncrement";
1915 public static final String POSITIVE_BLOCK_INCREMENT =
1916 "positiveBlockIncrement";
1917 public static final String NEGATIVE_UNIT_INCREMENT =
1918 "negativeUnitIncrement";
1919 public static final String NEGATIVE_BLOCK_INCREMENT =
1920 "negativeBlockIncrement";
1921 public static final String MIN_SCROLL_INCREMENT = "minScroll";
1922 public static final String MAX_SCROLL_INCREMENT = "maxScroll";
1923
1924
1925 Actions() {
1926 super(null);
1927 }
1928
1929 public Actions(String name) {
1930 super(name);
1931 }
1932
1933 public void actionPerformed(ActionEvent evt) {
1934 JSlider slider = (JSlider)evt.getSource();
1935 BasicSliderUI ui = (BasicSliderUI)BasicLookAndFeel.getUIOfType(
1936 slider.getUI(), BasicSliderUI.class);
1937 String name = getName();
1938
1939 if (ui == null) {
1940 return;
1941 }
1942 if (POSITIVE_UNIT_INCREMENT == name) {
1943 scroll(slider, ui, POSITIVE_SCROLL, false);
1944 } else if (NEGATIVE_UNIT_INCREMENT == name) {
1945 scroll(slider, ui, NEGATIVE_SCROLL, false);
1946 } else if (POSITIVE_BLOCK_INCREMENT == name) {
1947 scroll(slider, ui, POSITIVE_SCROLL, true);
1948 } else if (NEGATIVE_BLOCK_INCREMENT == name) {
1949 scroll(slider, ui, NEGATIVE_SCROLL, true);
1950 } else if (MIN_SCROLL_INCREMENT == name) {
1951 scroll(slider, ui, MIN_SCROLL, false);
1952 } else if (MAX_SCROLL_INCREMENT == name) {
1953 scroll(slider, ui, MAX_SCROLL, false);
1954 }
1955 }
1956
1957 private void scroll(JSlider slider, BasicSliderUI ui, int direction,
1958 boolean isBlock) {
1959 boolean invert = slider.getInverted();
1960
1961 if (direction == NEGATIVE_SCROLL || direction == POSITIVE_SCROLL) {
1962 if (invert) {
1963 direction = (direction == POSITIVE_SCROLL) ?
1964 NEGATIVE_SCROLL : POSITIVE_SCROLL;
1965 }
1966
1967 if (isBlock) {
1968 ui.scrollByBlock(direction);
1969 } else {
1970 ui.scrollByUnit(direction);
1971 }
1972 } else { // MIN or MAX
1973 if (invert) {
1974 direction = (direction == MIN_SCROLL) ?
1975 MAX_SCROLL : MIN_SCROLL;
1976 }
1977
1978 slider.setValue((direction == MIN_SCROLL) ?
1979 slider.getMinimum() : slider.getMaximum());
1980 }
1981 }
1982 }
1983}