blob: 7a9c4439f244e4ff03e28b2b5acffabe067088a0 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 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;
27
28import javax.swing.border.*;
29import javax.swing.event.*;
30import javax.swing.plaf.*;
31import javax.accessibility.*;
32
33import java.io.Serializable;
34import java.io.ObjectOutputStream;
35import java.io.ObjectInputStream;
36import java.io.IOException;
37
38import java.awt.Color;
39import java.awt.Font;
40import java.util.*;
41import java.beans.*;
42
43
44/**
45 * A component that lets the user graphically select a value by sliding
46 * a knob within a bounded interval.
47 * <p>
48 * The slider can show both
49 * major tick marks, and minor tick marks between the major ones. The number of
50 * values between the tick marks is controlled with
51 * <code>setMajorTickSpacing</code> and <code>setMinorTickSpacing</code>.
52 * Painting of tick marks is controlled by {@code setPaintTicks}.
53 * <p>
54 * Sliders can also print text labels at regular intervals (or at
55 * arbitrary locations) along the slider track. Painting of labels is
56 * controlled by {@code setLabelTable} and {@code setPaintLabels}.
57 * <p>
58 * For further information and examples see
59 * <a
60 href="http://java.sun.com/docs/books/tutorial/uiswing/components/slider.html">How to Use Sliders</a>,
61 * a section in <em>The Java Tutorial.</em>
62 * <p>
63 * <strong>Warning:</strong> Swing is not thread safe. For more
64 * information see <a
65 * href="package-summary.html#threading">Swing's Threading
66 * Policy</a>.
67 * <p>
68 * <strong>Warning:</strong>
69 * Serialized objects of this class will not be compatible with
70 * future Swing releases. The current serialization support is
71 * appropriate for short term storage or RMI between applications running
72 * the same version of Swing. As of 1.4, support for long term storage
73 * of all JavaBeans<sup><font size="-2">TM</font></sup>
74 * has been added to the <code>java.beans</code> package.
75 * Please see {@link java.beans.XMLEncoder}.
76 *
77 * @beaninfo
78 * attribute: isContainer false
79 * description: A component that supports selecting a integer value from a range.
80 *
81 * @author David Kloba
82 */
83public class JSlider extends JComponent implements SwingConstants, Accessible {
84 /**
85 * @see #getUIClassID
86 * @see #readObject
87 */
88 private static final String uiClassID = "SliderUI";
89
90 private boolean paintTicks = false;
91 private boolean paintTrack = true;
92 private boolean paintLabels = false;
93 private boolean isInverted = false;
94
95 /**
96 * The data model that handles the numeric maximum value,
97 * minimum value, and current-position value for the slider.
98 */
99 protected BoundedRangeModel sliderModel;
100
101 /**
102 * The number of values between the major tick marks -- the
103 * larger marks that break up the minor tick marks.
104 */
105 protected int majorTickSpacing;
106
107 /**
108 * The number of values between the minor tick marks -- the
109 * smaller marks that occur between the major tick marks.
110 * @see #setMinorTickSpacing
111 */
112 protected int minorTickSpacing;
113
114 /**
115 * If true, the knob (and the data value it represents)
116 * resolve to the closest tick mark next to where the user
117 * positioned the knob. The default is false.
118 * @see #setSnapToTicks
119 */
120 protected boolean snapToTicks = false;
121
122 /**
123 * If true, the knob (and the data value it represents)
124 * resolve to the closest slider value next to where the user
125 * positioned the knob.
126 */
127 boolean snapToValue = true;
128
129 /**
130 * Whether the slider is horizontal or vertical
131 * The default is horizontal.
132 *
133 * @see #setOrientation
134 */
135 protected int orientation;
136
137
138 /**
139 * {@code Dictionary} of what labels to draw at which values
140 */
141 private Dictionary labelTable;
142
143
144 /**
145 * The changeListener (no suffix) is the listener we add to the
146 * slider's model. This listener is initialized to the
147 * {@code ChangeListener} returned from {@code createChangeListener},
148 * which by default just forwards events
149 * to {@code ChangeListener}s (if any) added directly to the slider.
150 *
151 * @see #addChangeListener
152 * @see #createChangeListener
153 */
154 protected ChangeListener changeListener = createChangeListener();
155
156
157 /**
158 * Only one <code>ChangeEvent</code> is needed per slider instance since the
159 * event's only (read-only) state is the source property. The source
160 * of events generated here is always "this". The event is lazily
161 * created the first time that an event notification is fired.
162 *
163 * @see #fireStateChanged
164 */
165 protected transient ChangeEvent changeEvent = null;
166
167
168 private void checkOrientation(int orientation) {
169 switch (orientation) {
170 case VERTICAL:
171 case HORIZONTAL:
172 break;
173 default:
174 throw new IllegalArgumentException("orientation must be one of: VERTICAL, HORIZONTAL");
175 }
176 }
177
178
179 /**
180 * Creates a horizontal slider with the range 0 to 100 and
181 * an initial value of 50.
182 */
183 public JSlider() {
184 this(HORIZONTAL, 0, 100, 50);
185 }
186
187
188 /**
189 * Creates a slider using the specified orientation with the
190 * range {@code 0} to {@code 100} and an initial value of {@code 50}.
191 * The orientation can be
192 * either <code>SwingConstants.VERTICAL</code> or
193 * <code>SwingConstants.HORIZONTAL</code>.
194 *
195 * @param orientation the orientation of the slider
196 * @throws IllegalArgumentException if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL}
197 * @see #setOrientation
198 */
199 public JSlider(int orientation) {
200 this(orientation, 0, 100, 50);
201 }
202
203
204 /**
205 * Creates a horizontal slider using the specified min and max
206 * with an initial value equal to the average of the min plus max.
207 * <p>
208 * The <code>BoundedRangeModel</code> that holds the slider's data
209 * handles any issues that may arise from improperly setting the
210 * minimum and maximum values on the slider. See the
211 * {@code BoundedRangeModel} documentation for details.
212 *
213 * @param min the minimum value of the slider
214 * @param max the maximum value of the slider
215 *
216 * @see BoundedRangeModel
217 * @see #setMinimum
218 * @see #setMaximum
219 */
220 public JSlider(int min, int max) {
221 this(HORIZONTAL, min, max, (min + max) / 2);
222 }
223
224
225 /**
226 * Creates a horizontal slider using the specified min, max and value.
227 * <p>
228 * The <code>BoundedRangeModel</code> that holds the slider's data
229 * handles any issues that may arise from improperly setting the
230 * minimum, initial, and maximum values on the slider. See the
231 * {@code BoundedRangeModel} documentation for details.
232 *
233 * @param min the minimum value of the slider
234 * @param max the maximum value of the slider
235 * @param value the initial value of the slider
236 *
237 * @see BoundedRangeModel
238 * @see #setMinimum
239 * @see #setMaximum
240 * @see #setValue
241 */
242 public JSlider(int min, int max, int value) {
243 this(HORIZONTAL, min, max, value);
244 }
245
246
247 /**
248 * Creates a slider with the specified orientation and the
249 * specified minimum, maximum, and initial values.
250 * The orientation can be
251 * either <code>SwingConstants.VERTICAL</code> or
252 * <code>SwingConstants.HORIZONTAL</code>.
253 * <p>
254 * The <code>BoundedRangeModel</code> that holds the slider's data
255 * handles any issues that may arise from improperly setting the
256 * minimum, initial, and maximum values on the slider. See the
257 * {@code BoundedRangeModel} documentation for details.
258 *
259 * @param orientation the orientation of the slider
260 * @param min the minimum value of the slider
261 * @param max the maximum value of the slider
262 * @param value the initial value of the slider
263 *
264 * @throws IllegalArgumentException if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL}
265 *
266 * @see BoundedRangeModel
267 * @see #setOrientation
268 * @see #setMinimum
269 * @see #setMaximum
270 * @see #setValue
271 */
272 public JSlider(int orientation, int min, int max, int value)
273 {
274 checkOrientation(orientation);
275 this.orientation = orientation;
276 sliderModel = new DefaultBoundedRangeModel(value, 0, min, max);
277 sliderModel.addChangeListener(changeListener);
278 updateUI();
279 }
280
281
282 /**
283 * Creates a horizontal slider using the specified
284 * BoundedRangeModel.
285 */
286 public JSlider(BoundedRangeModel brm)
287 {
288 this.orientation = JSlider.HORIZONTAL;
289 setModel(brm);
290 sliderModel.addChangeListener(changeListener);
291 updateUI();
292 }
293
294
295 /**
296 * Gets the UI object which implements the L&F for this component.
297 *
298 * @return the SliderUI object that implements the Slider L&F
299 */
300 public SliderUI getUI() {
301 return(SliderUI)ui;
302 }
303
304
305 /**
306 * Sets the UI object which implements the L&F for this component.
307 *
308 * @param ui the SliderUI L&F object
309 * @see UIDefaults#getUI
310 * @beaninfo
311 * bound: true
312 * hidden: true
313 * attribute: visualUpdate true
314 * description: The UI object that implements the slider's LookAndFeel.
315 */
316 public void setUI(SliderUI ui) {
317 super.setUI(ui);
318 }
319
320
321 /**
322 * Resets the UI property to a value from the current look and feel.
323 *
324 * @see JComponent#updateUI
325 */
326 public void updateUI() {
327 setUI((SliderUI)UIManager.getUI(this));
328 // The labels preferred size may be derived from the font
329 // of the slider, so we must update the UI of the slider first, then
330 // that of labels. This way when setSize is called the right
331 // font is used.
332 updateLabelUIs();
333 }
334
335
336 /**
337 * Returns the name of the L&F class that renders this component.
338 *
339 * @return "SliderUI"
340 * @see JComponent#getUIClassID
341 * @see UIDefaults#getUI
342 */
343 public String getUIClassID() {
344 return uiClassID;
345 }
346
347
348 /**
349 * We pass Change events along to the listeners with the
350 * the slider (instead of the model itself) as the event source.
351 */
352 private class ModelListener implements ChangeListener, Serializable {
353 public void stateChanged(ChangeEvent e) {
354 fireStateChanged();
355 }
356 }
357
358
359 /**
360 * Subclasses that want to handle {@code ChangeEvent}s
361 * from the model differently
362 * can override this to return
363 * an instance of a custom <code>ChangeListener</code> implementation.
364 * The default {@code ChangeListener} simply calls the
365 * {@code fireStateChanged} method to forward {@code ChangeEvent}s
366 * to the {@code ChangeListener}s that have been added directly to the
367 * slider.
368 * @see #changeListener
369 * @see #fireStateChanged
370 * @see javax.swing.event.ChangeListener
371 * @see javax.swing.BoundedRangeModel
372 */
373 protected ChangeListener createChangeListener() {
374 return new ModelListener();
375 }
376
377
378 /**
379 * Adds a ChangeListener to the slider.
380 *
381 * @param l the ChangeListener to add
382 * @see #fireStateChanged
383 * @see #removeChangeListener
384 */
385 public void addChangeListener(ChangeListener l) {
386 listenerList.add(ChangeListener.class, l);
387 }
388
389
390 /**
391 * Removes a ChangeListener from the slider.
392 *
393 * @param l the ChangeListener to remove
394 * @see #fireStateChanged
395 * @see #addChangeListener
396
397 */
398 public void removeChangeListener(ChangeListener l) {
399 listenerList.remove(ChangeListener.class, l);
400 }
401
402
403 /**
404 * Returns an array of all the <code>ChangeListener</code>s added
405 * to this JSlider with addChangeListener().
406 *
407 * @return all of the <code>ChangeListener</code>s added or an empty
408 * array if no listeners have been added
409 * @since 1.4
410 */
411 public ChangeListener[] getChangeListeners() {
412 return (ChangeListener[])listenerList.getListeners(
413 ChangeListener.class);
414 }
415
416
417 /**
418 * Send a {@code ChangeEvent}, whose source is this {@code JSlider}, to
419 * all {@code ChangeListener}s that have registered interest in
420 * {@code ChangeEvent}s.
421 * This method is called each time a {@code ChangeEvent} is received from
422 * the model.
423 * <p>
424 * The event instance is created if necessary, and stored in
425 * {@code changeEvent}.
426 *
427 * @see #addChangeListener
428 * @see EventListenerList
429 */
430 protected void fireStateChanged() {
431 Object[] listeners = listenerList.getListenerList();
432 for (int i = listeners.length - 2; i >= 0; i -= 2) {
433 if (listeners[i]==ChangeListener.class) {
434 if (changeEvent == null) {
435 changeEvent = new ChangeEvent(this);
436 }
437 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
438 }
439 }
440 }
441
442
443 /**
444 * Returns the {@code BoundedRangeModel} that handles the slider's three
445 * fundamental properties: minimum, maximum, value.
446 *
447 * @return the data model for this component
448 * @see #setModel
449 * @see BoundedRangeModel
450 */
451 public BoundedRangeModel getModel() {
452 return sliderModel;
453 }
454
455
456 /**
457 * Sets the {@code BoundedRangeModel} that handles the slider's three
458 * fundamental properties: minimum, maximum, value.
459 *<p>
460 * Attempts to pass a {@code null} model to this method result in
461 * undefined behavior, and, most likely, exceptions.
462 *
463 * @param newModel the new, {@code non-null} <code>BoundedRangeModel</code> to use
464 *
465 * @see #getModel
466 * @see BoundedRangeModel
467 * @beaninfo
468 * bound: true
469 * description: The sliders BoundedRangeModel.
470 */
471 public void setModel(BoundedRangeModel newModel)
472 {
473 BoundedRangeModel oldModel = getModel();
474
475 if (oldModel != null) {
476 oldModel.removeChangeListener(changeListener);
477 }
478
479 sliderModel = newModel;
480
481 if (newModel != null) {
482 newModel.addChangeListener(changeListener);
483
484 if (accessibleContext != null) {
485 accessibleContext.firePropertyChange(
486 AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
487 (oldModel == null
488 ? null : new Integer(oldModel.getValue())),
489 (newModel == null
490 ? null : new Integer(newModel.getValue())));
491 }
492 }
493
494 firePropertyChange("model", oldModel, sliderModel);
495 }
496
497
498 /**
499 * Returns the slider's current value
500 * from the {@code BoundedRangeModel}.
501 *
502 * @return the current value of the slider
503 * @see #setValue
504 * @see BoundedRangeModel#getValue
505 */
506 public int getValue() {
507 return getModel().getValue();
508 }
509
510 /**
511 * Sets the slider's current value to {@code n}. This method
512 * forwards the new value to the model.
513 * <p>
514 * The data model (an instance of {@code BoundedRangeModel})
515 * handles any mathematical
516 * issues arising from assigning faulty values. See the
517 * {@code BoundedRangeModel} documentation for details.
518 * <p>
519 * If the new value is different from the previous value,
520 * all change listeners are notified.
521 *
522 * @param n the new value
523 * @see #getValue
524 * @see #addChangeListener
525 * @see BoundedRangeModel#setValue
526 * @beaninfo
527 * preferred: true
528 * description: The sliders current value.
529 */
530 public void setValue(int n) {
531 BoundedRangeModel m = getModel();
532 int oldValue = m.getValue();
533 if (oldValue == n) {
534 return;
535 }
536 m.setValue(n);
537
538 if (accessibleContext != null) {
539 accessibleContext.firePropertyChange(
540 AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
541 new Integer(oldValue),
542 new Integer(m.getValue()));
543 }
544 }
545
546
547 /**
548 * Returns the minimum value supported by the slider
549 * from the <code>BoundedRangeModel</code>.
550 *
551 * @return the value of the model's minimum property
552 * @see #setMinimum
553 * @see BoundedRangeModel#getMinimum
554 */
555 public int getMinimum() {
556 return getModel().getMinimum();
557 }
558
559
560 /**
561 * Sets the slider's minimum value to {@code minimum}. This method
562 * forwards the new minimum value to the model.
563 * <p>
564 * The data model (an instance of {@code BoundedRangeModel})
565 * handles any mathematical
566 * issues arising from assigning faulty values. See the
567 * {@code BoundedRangeModel} documentation for details.
568 * <p>
569 * If the new minimum value is different from the previous minimum value,
570 * all change listeners are notified.
571 *
572 * @param minimum the new minimum
573 * @see #getMinimum
574 * @see #addChangeListener
575 * @see BoundedRangeModel#setMinimum
576 * @beaninfo
577 * bound: true
578 * preferred: true
579 * description: The sliders minimum value.
580 */
581 public void setMinimum(int minimum) {
582 int oldMin = getModel().getMinimum();
583 getModel().setMinimum(minimum);
584 firePropertyChange( "minimum", new Integer( oldMin ), new Integer( minimum ) );
585 }
586
587
588 /**
589 * Returns the maximum value supported by the slider
590 * from the <code>BoundedRangeModel</code>.
591 *
592 * @return the value of the model's maximum property
593 * @see #setMaximum
594 * @see BoundedRangeModel#getMaximum
595 */
596 public int getMaximum() {
597 return getModel().getMaximum();
598 }
599
600
601 /**
602 * Sets the slider's maximum value to {@code maximum}. This method
603 * forwards the new maximum value to the model.
604 * <p>
605 * The data model (an instance of {@code BoundedRangeModel})
606 * handles any mathematical
607 * issues arising from assigning faulty values. See the
608 * {@code BoundedRangeModel} documentation for details.
609 * <p>
610 * If the new maximum value is different from the previous maximum value,
611 * all change listeners are notified.
612 *
613 * @param maximum the new maximum
614 * @see #getMaximum
615 * @see #addChangeListener
616 * @see BoundedRangeModel#setMaximum
617 * @beaninfo
618 * bound: true
619 * preferred: true
620 * description: The sliders maximum value.
621 */
622 public void setMaximum(int maximum) {
623 int oldMax = getModel().getMaximum();
624 getModel().setMaximum(maximum);
625 firePropertyChange( "maximum", new Integer( oldMax ), new Integer( maximum ) );
626 }
627
628
629 /**
630 * Returns the {@code valueIsAdjusting} property from the model. For
631 * details on how this is used, see the {@code setValueIsAdjusting}
632 * documentation.
633 *
634 * @return the value of the model's {@code valueIsAdjusting} property
635 * @see #setValueIsAdjusting
636 */
637 public boolean getValueIsAdjusting() {
638 return getModel().getValueIsAdjusting();
639 }
640
641
642 /**
643 * Sets the model's {@code valueIsAdjusting} property. Slider look and
644 * feel implementations should set this property to {@code true} when
645 * a knob drag begins, and to {@code false} when the drag ends. The
646 * slider model will not generate {@code ChangeEvent}s while
647 * {@code valueIsAdjusting} is {@code true}.
648 *
649 * @param b the new value for the {@code valueIsAdjusting} property
650 * @see #getValueIsAdjusting
651 * @see BoundedRangeModel#setValueIsAdjusting
652 * @beaninfo
653 * expert: true
654 * description: True if the slider knob is being dragged.
655 */
656 public void setValueIsAdjusting(boolean b) {
657 BoundedRangeModel m = getModel();
658 boolean oldValue = m.getValueIsAdjusting();
659 m.setValueIsAdjusting(b);
660
661 if ((oldValue != b) && (accessibleContext != null)) {
662 accessibleContext.firePropertyChange(
663 AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
664 ((oldValue) ? AccessibleState.BUSY : null),
665 ((b) ? AccessibleState.BUSY : null));
666 }
667 }
668
669
670 /**
671 * Returns the "extent" from the <code>BoundedRangeModel</code>.
672 * This respresents the range of values "covered" by the knob.
673 *
674 * @return an int representing the extent
675 * @see #setExtent
676 * @see BoundedRangeModel#getExtent
677 */
678 public int getExtent() {
679 return getModel().getExtent();
680 }
681
682
683 /**
684 * Sets the size of the range "covered" by the knob. Most look
685 * and feel implementations will change the value by this amount
686 * if the user clicks on either side of the knob. This method just
687 * forwards the new extent value to the model.
688 * <p>
689 * The data model (an instance of {@code BoundedRangeModel})
690 * handles any mathematical
691 * issues arising from assigning faulty values. See the
692 * {@code BoundedRangeModel} documentation for details.
693 * <p>
694 * If the new extent value is different from the previous extent value,
695 * all change listeners are notified.
696 *
697 * @param extent the new extent
698 * @see #getExtent
699 * @see BoundedRangeModel#setExtent
700 * @beaninfo
701 * expert: true
702 * description: Size of the range covered by the knob.
703 */
704 public void setExtent(int extent) {
705 getModel().setExtent(extent);
706 }
707
708
709 /**
710 * Return this slider's vertical or horizontal orientation.
711 * @return {@code SwingConstants.VERTICAL} or
712 * {@code SwingConstants.HORIZONTAL}
713 * @see #setOrientation
714 */
715 public int getOrientation() {
716 return orientation;
717 }
718
719
720 /**
721 * Set the slider's orientation to either {@code SwingConstants.VERTICAL} or
722 * {@code SwingConstants.HORIZONTAL}.
723 *
724 * @param orientation {@code HORIZONTAL} or {@code VERTICAL}
725 * @throws IllegalArgumentException if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL}
726 * @see #getOrientation
727 * @beaninfo
728 * preferred: true
729 * bound: true
730 * attribute: visualUpdate true
731 * description: Set the scrollbars orientation to either VERTICAL or HORIZONTAL.
732 * enum: VERTICAL JSlider.VERTICAL
733 * HORIZONTAL JSlider.HORIZONTAL
734 *
735 */
736 public void setOrientation(int orientation)
737 {
738 checkOrientation(orientation);
739 int oldValue = this.orientation;
740 this.orientation = orientation;
741 firePropertyChange("orientation", oldValue, orientation);
742
743 if ((oldValue != orientation) && (accessibleContext != null)) {
744 accessibleContext.firePropertyChange(
745 AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
746 ((oldValue == VERTICAL)
747 ? AccessibleState.VERTICAL : AccessibleState.HORIZONTAL),
748 ((orientation == VERTICAL)
749 ? AccessibleState.VERTICAL : AccessibleState.HORIZONTAL));
750 }
751 if (orientation != oldValue) {
752 revalidate();
753 }
754 }
755
756
757 /**
758 * {@inheritDoc}
759 *
760 * @since 1.6
761 */
762 public void setFont(Font font) {
763 super.setFont(font);
764 updateLabelSizes();
765 }
766
767
768 /**
769 * Returns the dictionary of what labels to draw at which values.
770 *
771 * @return the <code>Dictionary</code> containing labels and
772 * where to draw them
773 */
774 public Dictionary getLabelTable() {
775/*
776 if ( labelTable == null && getMajorTickSpacing() > 0 ) {
777 setLabelTable( createStandardLabels( getMajorTickSpacing() ) );
778 }
779*/
780 return labelTable;
781 }
782
783
784 /**
785 * Used to specify what label will be drawn at any given value.
786 * The key-value pairs are of this format:
787 * <code>{ Integer value, java.swing.JComponent label }</code>.
788 * <p>
789 * An easy way to generate a standard table of value labels is by using the
790 * {@code createStandardLabels} method.
791 * <p>
792 * Once the labels have been set, this method calls {@link #updateLabelUIs}.
793 * Note that the labels are only painted if the {@code paintLabels}
794 * property is {@code true}.
795 *
796 * @param labels new {@code Dictionary} of labels, or {@code null} to
797 * remove all labels
798 * @see #createStandardLabels(int)
799 * @see #getLabelTable
800 * @see #setPaintLabels
801 * @beaninfo
802 * hidden: true
803 * bound: true
804 * attribute: visualUpdate true
805 * description: Specifies what labels will be drawn for any given value.
806 */
807 public void setLabelTable( Dictionary labels ) {
808 Dictionary oldTable = labelTable;
809 labelTable = labels;
810 updateLabelUIs();
811 firePropertyChange("labelTable", oldTable, labelTable );
812 if (labels != oldTable) {
813 revalidate();
814 repaint();
815 }
816 }
817
818
819 /**
820 * Updates the UIs for the labels in the label table by calling
821 * {@code updateUI} on each label. The UIs are updated from
822 * the current look and feel. The labels are also set to their
823 * preferred size.
824 *
825 * @see #setLabelTable
826 * @see JComponent#updateUI
827 */
828 protected void updateLabelUIs() {
829 if ( getLabelTable() == null ) {
830 return;
831 }
832 Enumeration labels = getLabelTable().keys();
833 while ( labels.hasMoreElements() ) {
834 Object value = getLabelTable().get( labels.nextElement() );
835 if ( value instanceof JComponent ) {
836 JComponent component = (JComponent)value;
837 component.updateUI();
838 component.setSize( component.getPreferredSize() );
839 }
840 }
841 }
842
843 private void updateLabelSizes() {
844 Dictionary labelTable = getLabelTable();
845 if (labelTable != null) {
846 Enumeration labels = labelTable.elements();
847 while (labels.hasMoreElements()) {
848 Object value = labels.nextElement();
849 if (value instanceof JComponent) {
850 JComponent component = (JComponent)value;
851 component.setSize(component.getPreferredSize());
852 }
853 }
854 }
855 }
856
857
858 /**
859 * Creates a {@code Hashtable} of numerical text labels, starting at the
860 * slider minimum, and using the increment specified.
861 * For example, if you call <code>createStandardLabels( 10 )</code>
862 * and the slider minimum is zero,
863 * then labels will be created for the values 0, 10, 20, 30, and so on.
864 * <p>
865 * For the labels to be drawn on the slider, the returned {@code Hashtable}
866 * must be passed into {@code setLabelTable}, and {@code setPaintLabels}
867 * must be set to {@code true}.
868 * <p>
869 * For further details on the makeup of the returned {@code Hashtable}, see
870 * the {@code setLabelTable} documentation.
871 *
872 * @param increment distance between labels in the generated hashtable
873 * @return a new {@code Hashtable} of labels
874 * @see #setLabelTable
875 * @see #setPaintLabels
876 * @throws IllegalArgumentException if {@code increment} is less than or
877 * equal to zero
878 */
879 public Hashtable createStandardLabels( int increment ) {
880 return createStandardLabels( increment, getMinimum() );
881 }
882
883
884 /**
885 * Creates a {@code Hashtable} of numerical text labels, starting at the
886 * starting point specified, and using the increment specified.
887 * For example, if you call
888 * <code>createStandardLabels( 10, 2 )</code>,
889 * then labels will be created for the values 2, 12, 22, 32, and so on.
890 * <p>
891 * For the labels to be drawn on the slider, the returned {@code Hashtable}
892 * must be passed into {@code setLabelTable}, and {@code setPaintLabels}
893 * must be set to {@code true}.
894 * <p>
895 * For further details on the makeup of the returned {@code Hashtable}, see
896 * the {@code setLabelTable} documentation.
897 *
898 * @param increment distance between labels in the generated hashtable
899 * @param start value at which the labels will begin
900 * @return a new {@code Hashtable} of labels
901 * @see #setLabelTable
902 * @see #setPaintLabels
903 * @exception IllegalArgumentException if {@code start} is
904 * out of range, or if {@code increment} is less than or equal
905 * to zero
906 */
907 public Hashtable createStandardLabels( int increment, int start ) {
908 if ( start > getMaximum() || start < getMinimum() ) {
909 throw new IllegalArgumentException( "Slider label start point out of range." );
910 }
911
912 if ( increment <= 0 ) {
913 throw new IllegalArgumentException( "Label incremement must be > 0" );
914 }
915
916 class SmartHashtable extends Hashtable implements PropertyChangeListener {
917 int increment = 0;
918 int start = 0;
919 boolean startAtMin = false;
920
921 class LabelUIResource extends JLabel implements UIResource {
922 public LabelUIResource( String text, int alignment ) {
923 super( text, alignment );
924 setName("Slider.label");
925 }
926
927 public Font getFont() {
928 Font font = super.getFont();
929 if (font != null && !(font instanceof UIResource)) {
930 return font;
931 }
932 return JSlider.this.getFont();
933 }
934
935 public Color getForeground() {
936 Color fg = super.getForeground();
937 if (fg != null && !(fg instanceof UIResource)) {
938 return fg;
939 }
940 if (!(JSlider.this.getForeground() instanceof UIResource)) {
941 return JSlider.this.getForeground();
942 }
943 return fg;
944 }
945 }
946
947 public SmartHashtable( int increment, int start ) {
948 super();
949 this.increment = increment;
950 this.start = start;
951 startAtMin = start == getMinimum();
952 createLabels();
953 }
954
955 public void propertyChange( PropertyChangeEvent e ) {
956 if ( e.getPropertyName().equals( "minimum" ) && startAtMin ) {
957 start = getMinimum();
958 }
959
960 if ( e.getPropertyName().equals( "minimum" ) ||
961 e.getPropertyName().equals( "maximum" ) ) {
962
963 Enumeration keys = getLabelTable().keys();
964 Object key = null;
965 Hashtable hashtable = new Hashtable();
966
967 // Save the labels that were added by the developer
968 while ( keys.hasMoreElements() ) {
969 key = keys.nextElement();
970 Object value = getLabelTable().get( key );
971 if ( !(value instanceof LabelUIResource) ) {
972 hashtable.put( key, value );
973 }
974 }
975
976 clear();
977 createLabels();
978
979 // Add the saved labels
980 keys = hashtable.keys();
981 while ( keys.hasMoreElements() ) {
982 key = keys.nextElement();
983 put( key, hashtable.get( key ) );
984 }
985
986 ((JSlider)e.getSource()).setLabelTable( this );
987 }
988 }
989
990 void createLabels() {
991 for ( int labelIndex = start; labelIndex <= getMaximum(); labelIndex += increment ) {
992 put( new Integer( labelIndex ), new LabelUIResource( ""+labelIndex, JLabel.CENTER ) );
993 }
994 }
995 }
996
997 SmartHashtable table = new SmartHashtable( increment, start );
998
999 if ( getLabelTable() != null && (getLabelTable() instanceof PropertyChangeListener) ) {
1000 removePropertyChangeListener( (PropertyChangeListener)getLabelTable() );
1001 }
1002
1003 addPropertyChangeListener( table );
1004
1005 return table;
1006 }
1007
1008
1009 /**
1010 * Returns true if the value-range shown for the slider is reversed,
1011 *
1012 * @return true if the slider values are reversed from their normal order
1013 * @see #setInverted
1014 */
1015 public boolean getInverted() {
1016 return isInverted;
1017 }
1018
1019
1020 /**
1021 * Specify true to reverse the value-range shown for the slider and false to
1022 * put the value range in the normal order. The order depends on the
1023 * slider's <code>ComponentOrientation</code> property. Normal (non-inverted)
1024 * horizontal sliders with a <code>ComponentOrientation</code> value of
1025 * <code>LEFT_TO_RIGHT</code> have their maximum on the right.
1026 * Normal horizontal sliders with a <code>ComponentOrientation</code> value of
1027 * <code>RIGHT_TO_LEFT</code> have their maximum on the left. Normal vertical
1028 * sliders have their maximum on the top. These labels are reversed when the
1029 * slider is inverted.
1030 * <p>
1031 * By default, the value of this property is {@code false}.
1032 *
1033 * @param b true to reverse the slider values from their normal order
1034 * @beaninfo
1035 * bound: true
1036 * attribute: visualUpdate true
1037 * description: If true reverses the slider values from their normal order
1038 *
1039 */
1040 public void setInverted( boolean b ) {
1041 boolean oldValue = isInverted;
1042 isInverted = b;
1043 firePropertyChange("inverted", oldValue, isInverted);
1044 if (b != oldValue) {
1045 repaint();
1046 }
1047 }
1048
1049
1050 /**
1051 * This method returns the major tick spacing. The number that is returned
1052 * represents the distance, measured in values, between each major tick mark.
1053 * If you have a slider with a range from 0 to 50 and the major tick spacing
1054 * is set to 10, you will get major ticks next to the following values:
1055 * 0, 10, 20, 30, 40, 50.
1056 *
1057 * @return the number of values between major ticks
1058 * @see #setMajorTickSpacing
1059 */
1060 public int getMajorTickSpacing() {
1061 return majorTickSpacing;
1062 }
1063
1064
1065 /**
1066 * This method sets the major tick spacing. The number that is passed in
1067 * represents the distance, measured in values, between each major tick mark.
1068 * If you have a slider with a range from 0 to 50 and the major tick spacing
1069 * is set to 10, you will get major ticks next to the following values:
1070 * 0, 10, 20, 30, 40, 50.
1071 * <p>
1072 * In order for major ticks to be painted, {@code setPaintTicks} must be
1073 * set to {@code true}.
1074 * <p>
1075 * This method will also set up a label table for you.
1076 * If there is not already a label table, and the major tick spacing is
1077 * {@code > 0}, and {@code getPaintLabels} returns
1078 * {@code true}, a standard label table will be generated (by calling
1079 * {@code createStandardLabels}) with labels at the major tick marks.
1080 * For the example above, you would get text labels: "0",
1081 * "10", "20", "30", "40", "50".
1082 * The label table is then set on the slider by calling
1083 * {@code setLabelTable}.
1084 *
1085 * @param n new value for the {@code majorTickSpacing} property
1086 * @see #getMajorTickSpacing
1087 * @see #setPaintTicks
1088 * @see #setLabelTable
1089 * @see #createStandardLabels(int)
1090 * @beaninfo
1091 * bound: true
1092 * attribute: visualUpdate true
1093 * description: Sets the number of values between major tick marks.
1094 *
1095 */
1096 public void setMajorTickSpacing(int n) {
1097 int oldValue = majorTickSpacing;
1098 majorTickSpacing = n;
1099 if ( labelTable == null && getMajorTickSpacing() > 0 && getPaintLabels() ) {
1100 setLabelTable( createStandardLabels( getMajorTickSpacing() ) );
1101 }
1102 firePropertyChange("majorTickSpacing", oldValue, majorTickSpacing);
1103 if (majorTickSpacing != oldValue && getPaintTicks()) {
1104 repaint();
1105 }
1106 }
1107
1108
1109
1110 /**
1111 * This method returns the minor tick spacing. The number that is returned
1112 * represents the distance, measured in values, between each minor tick mark.
1113 * If you have a slider with a range from 0 to 50 and the minor tick spacing
1114 * is set to 10, you will get minor ticks next to the following values:
1115 * 0, 10, 20, 30, 40, 50.
1116 *
1117 * @return the number of values between minor ticks
1118 * @see #getMinorTickSpacing
1119 */
1120 public int getMinorTickSpacing() {
1121 return minorTickSpacing;
1122 }
1123
1124
1125 /**
1126 * This method sets the minor tick spacing. The number that is passed in
1127 * represents the distance, measured in values, between each minor tick mark.
1128 * If you have a slider with a range from 0 to 50 and the minor tick spacing
1129 * is set to 10, you will get minor ticks next to the following values:
1130 * 0, 10, 20, 30, 40, 50.
1131 * <p>
1132 * In order for minor ticks to be painted, {@code setPaintTicks} must be
1133 * set to {@code true}.
1134 *
1135 * @param n new value for the {@code minorTickSpacing} property
1136 * @see #getMinorTickSpacing
1137 * @see #setPaintTicks
1138 * @beaninfo
1139 * bound: true
1140 * attribute: visualUpdate true
1141 * description: Sets the number of values between minor tick marks.
1142 */
1143 public void setMinorTickSpacing(int n) {
1144 int oldValue = minorTickSpacing;
1145 minorTickSpacing = n;
1146 firePropertyChange("minorTickSpacing", oldValue, minorTickSpacing);
1147 if (minorTickSpacing != oldValue && getPaintTicks()) {
1148 repaint();
1149 }
1150 }
1151
1152
1153 /**
1154 * Returns true if the knob (and the data value it represents)
1155 * resolve to the closest tick mark next to where the user
1156 * positioned the knob.
1157 *
1158 * @return true if the value snaps to the nearest tick mark, else false
1159 * @see #setSnapToTicks
1160 */
1161 public boolean getSnapToTicks() {
1162 return snapToTicks;
1163 }
1164
1165
1166 /**
1167 * Returns true if the knob (and the data value it represents)
1168 * resolve to the closest slider value next to where the user
1169 * positioned the knob.
1170 *
1171 * @return true if the value snaps to the nearest slider value, else false
1172 * @see #setSnapToValue
1173 */
1174 boolean getSnapToValue() {
1175 return snapToValue;
1176 }
1177
1178
1179 /**
1180 * Specifying true makes the knob (and the data value it represents)
1181 * resolve to the closest tick mark next to where the user
1182 * positioned the knob.
1183 * By default, this property is {@code false}.
1184 *
1185 * @param b true to snap the knob to the nearest tick mark
1186 * @see #getSnapToTicks
1187 * @beaninfo
1188 * bound: true
1189 * description: If true snap the knob to the nearest tick mark.
1190 */
1191 public void setSnapToTicks(boolean b) {
1192 boolean oldValue = snapToTicks;
1193 snapToTicks = b;
1194 firePropertyChange("snapToTicks", oldValue, snapToTicks);
1195 }
1196
1197
1198 /**
1199 * Specifying true makes the knob (and the data value it represents)
1200 * resolve to the closest slider value next to where the user
1201 * positioned the knob. If the {@code snapToTicks} property has also been
1202 * set to {@code true}, the snap-to-ticks behavior will prevail.
1203 * By default, the snapToValue property is {@code true}.
1204 *
1205 * @param b true to snap the knob to the nearest slider value
1206 * @see #getSnapToValue
1207 * @see #setSnapToTicks
1208 * @beaninfo
1209 * bound: true
1210 * description: If true snap the knob to the nearest slider value.
1211 */
1212 void setSnapToValue(boolean b) {
1213 boolean oldValue = snapToValue;
1214 snapToValue = b;
1215 firePropertyChange("snapToValue", oldValue, snapToValue);
1216 }
1217
1218
1219 /**
1220 * Tells if tick marks are to be painted.
1221 * @return true if tick marks are painted, else false
1222 * @see #setPaintTicks
1223 */
1224 public boolean getPaintTicks() {
1225 return paintTicks;
1226 }
1227
1228
1229 /**
1230 * Determines whether tick marks are painted on the slider.
1231 * By default, this property is {@code false}.
1232 *
1233 * @param b whether or not tick marks should be painted
1234 * @see #getPaintTicks
1235 * @beaninfo
1236 * bound: true
1237 * attribute: visualUpdate true
1238 * description: If true tick marks are painted on the slider.
1239 */
1240 public void setPaintTicks(boolean b) {
1241 boolean oldValue = paintTicks;
1242 paintTicks = b;
1243 firePropertyChange("paintTicks", oldValue, paintTicks);
1244 if (paintTicks != oldValue) {
1245 revalidate();
1246 repaint();
1247 }
1248 }
1249
1250 /**
1251 * Tells if the track (area the slider slides in) is to be painted.
1252 * @return true if track is painted, else false
1253 * @see #setPaintTrack
1254 */
1255 public boolean getPaintTrack() {
1256 return paintTrack;
1257 }
1258
1259
1260 /**
1261 * Determines whether the track is painted on the slider.
1262 * By default, this property is {@code true}.
1263 *
1264 * @param b whether or not to paint the slider track
1265 * @see #getPaintTrack
1266 * @beaninfo
1267 * bound: true
1268 * attribute: visualUpdate true
1269 * description: If true, the track is painted on the slider.
1270 */
1271 public void setPaintTrack(boolean b) {
1272 boolean oldValue = paintTrack;
1273 paintTrack = b;
1274 firePropertyChange("paintTrack", oldValue, paintTrack);
1275 if (paintTrack != oldValue) {
1276 repaint();
1277 }
1278 }
1279
1280
1281 /**
1282 * Tells if labels are to be painted.
1283 * @return true if labels are painted, else false
1284 * @see #setPaintLabels
1285 */
1286 public boolean getPaintLabels() {
1287 return paintLabels;
1288 }
1289
1290
1291 /**
1292 * Determines whether labels are painted on the slider.
1293 * <p>
1294 * This method will also set up a label table for you.
1295 * If there is not already a label table, and the major tick spacing is
1296 * {@code > 0},
1297 * a standard label table will be generated (by calling
1298 * {@code createStandardLabels}) with labels at the major tick marks.
1299 * The label table is then set on the slider by calling
1300 * {@code setLabelTable}.
1301 * <p>
1302 * By default, this property is {@code false}.
1303 *
1304 * @param b whether or not to paint labels
1305 * @see #getPaintLabels
1306 * @see #getLabelTable
1307 * @see #createStandardLabels(int)
1308 * @beaninfo
1309 * bound: true
1310 * attribute: visualUpdate true
1311 * description: If true labels are painted on the slider.
1312 */
1313 public void setPaintLabels(boolean b) {
1314 boolean oldValue = paintLabels;
1315 paintLabels = b;
1316 if ( labelTable == null && getMajorTickSpacing() > 0 ) {
1317 setLabelTable( createStandardLabels( getMajorTickSpacing() ) );
1318 }
1319 firePropertyChange("paintLabels", oldValue, paintLabels);
1320 if (paintLabels != oldValue) {
1321 revalidate();
1322 repaint();
1323 }
1324 }
1325
1326
1327 /**
1328 * See readObject() and writeObject() in JComponent for more
1329 * information about serialization in Swing.
1330 */
1331 private void writeObject(ObjectOutputStream s) throws IOException {
1332 s.defaultWriteObject();
1333 if (getUIClassID().equals(uiClassID)) {
1334 byte count = JComponent.getWriteObjCounter(this);
1335 JComponent.setWriteObjCounter(this, --count);
1336 if (count == 0 && ui != null) {
1337 ui.installUI(this);
1338 }
1339 }
1340 }
1341
1342
1343 /**
1344 * Returns a string representation of this JSlider. This method
1345 * is intended to be used only for debugging purposes, and the
1346 * content and format of the returned string may vary between
1347 * implementations. The returned string may be empty but may not
1348 * be <code>null</code>.
1349 *
1350 * @return a string representation of this JSlider.
1351 */
1352 protected String paramString() {
1353 String paintTicksString = (paintTicks ?
1354 "true" : "false");
1355 String paintTrackString = (paintTrack ?
1356 "true" : "false");
1357 String paintLabelsString = (paintLabels ?
1358 "true" : "false");
1359 String isInvertedString = (isInverted ?
1360 "true" : "false");
1361 String snapToTicksString = (snapToTicks ?
1362 "true" : "false");
1363 String snapToValueString = (snapToValue ?
1364 "true" : "false");
1365 String orientationString = (orientation == HORIZONTAL ?
1366 "HORIZONTAL" : "VERTICAL");
1367
1368 return super.paramString() +
1369 ",isInverted=" + isInvertedString +
1370 ",majorTickSpacing=" + majorTickSpacing +
1371 ",minorTickSpacing=" + minorTickSpacing +
1372 ",orientation=" + orientationString +
1373 ",paintLabels=" + paintLabelsString +
1374 ",paintTicks=" + paintTicksString +
1375 ",paintTrack=" + paintTrackString +
1376 ",snapToTicks=" + snapToTicksString +
1377 ",snapToValue=" + snapToValueString;
1378 }
1379
1380
1381/////////////////
1382// Accessibility support
1383////////////////
1384
1385 /**
1386 * Gets the AccessibleContext associated with this JSlider.
1387 * For sliders, the AccessibleContext takes the form of an
1388 * AccessibleJSlider.
1389 * A new AccessibleJSlider instance is created if necessary.
1390 *
1391 * @return an AccessibleJSlider that serves as the
1392 * AccessibleContext of this JSlider
1393 */
1394 public AccessibleContext getAccessibleContext() {
1395 if (accessibleContext == null) {
1396 accessibleContext = new AccessibleJSlider();
1397 }
1398 return accessibleContext;
1399 }
1400
1401 /**
1402 * This class implements accessibility support for the
1403 * <code>JSlider</code> class. It provides an implementation of the
1404 * Java Accessibility API appropriate to slider user-interface elements.
1405 * <p>
1406 * <strong>Warning:</strong>
1407 * Serialized objects of this class will not be compatible with
1408 * future Swing releases. The current serialization support is
1409 * appropriate for short term storage or RMI between applications running
1410 * the same version of Swing. As of 1.4, support for long term storage
1411 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1412 * has been added to the <code>java.beans</code> package.
1413 * Please see {@link java.beans.XMLEncoder}.
1414 */
1415 protected class AccessibleJSlider extends AccessibleJComponent
1416 implements AccessibleValue {
1417
1418 /**
1419 * Get the state set of this object.
1420 *
1421 * @return an instance of AccessibleState containing the current state
1422 * of the object
1423 * @see AccessibleState
1424 */
1425 public AccessibleStateSet getAccessibleStateSet() {
1426 AccessibleStateSet states = super.getAccessibleStateSet();
1427 if (getValueIsAdjusting()) {
1428 states.add(AccessibleState.BUSY);
1429 }
1430 if (getOrientation() == VERTICAL) {
1431 states.add(AccessibleState.VERTICAL);
1432 }
1433 else {
1434 states.add(AccessibleState.HORIZONTAL);
1435 }
1436 return states;
1437 }
1438
1439 /**
1440 * Get the role of this object.
1441 *
1442 * @return an instance of AccessibleRole describing the role of the object
1443 */
1444 public AccessibleRole getAccessibleRole() {
1445 return AccessibleRole.SLIDER;
1446 }
1447
1448 /**
1449 * Get the AccessibleValue associated with this object. In the
1450 * implementation of the Java Accessibility API for this class,
1451 * return this object, which is responsible for implementing the
1452 * AccessibleValue interface on behalf of itself.
1453 *
1454 * @return this object
1455 */
1456 public AccessibleValue getAccessibleValue() {
1457 return this;
1458 }
1459
1460 /**
1461 * Get the accessible value of this object.
1462 *
1463 * @return The current value of this object.
1464 */
1465 public Number getCurrentAccessibleValue() {
1466 return new Integer(getValue());
1467 }
1468
1469 /**
1470 * Set the value of this object as a Number.
1471 *
1472 * @return True if the value was set.
1473 */
1474 public boolean setCurrentAccessibleValue(Number n) {
1475 // TIGER - 4422535
1476 if (n == null) {
1477 return false;
1478 }
1479 setValue(n.intValue());
1480 return true;
1481 }
1482
1483 /**
1484 * Get the minimum accessible value of this object.
1485 *
1486 * @return The minimum value of this object.
1487 */
1488 public Number getMinimumAccessibleValue() {
1489 return new Integer(getMinimum());
1490 }
1491
1492 /**
1493 * Get the maximum accessible value of this object.
1494 *
1495 * @return The maximum value of this object.
1496 */
1497 public Number getMaximumAccessibleValue() {
1498 // TIGER - 4422362
1499 BoundedRangeModel model = JSlider.this.getModel();
1500 return new Integer(model.getMaximum() - model.getExtent());
1501 }
1502 } // AccessibleJSlider
1503}