001/* JSlider.java --
002   Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import gnu.java.lang.CPStringBuilder;
042
043import java.awt.MenuContainer;
044import java.awt.image.ImageObserver;
045import java.beans.PropertyChangeEvent;
046import java.io.Serializable;
047import java.util.Dictionary;
048import java.util.Enumeration;
049import java.util.Hashtable;
050
051import javax.accessibility.Accessible;
052import javax.accessibility.AccessibleContext;
053import javax.accessibility.AccessibleRole;
054import javax.accessibility.AccessibleState;
055import javax.accessibility.AccessibleStateSet;
056import javax.accessibility.AccessibleValue;
057import javax.swing.event.ChangeEvent;
058import javax.swing.event.ChangeListener;
059import javax.swing.plaf.SliderUI;
060import javax.swing.plaf.UIResource;
061
062/**
063 * A visual component that allows selection of a value within a
064 * range by adjusting a thumb in a track. The values for the minimum,
065 * maximum, extent and value are stored in a {@link
066 * DefaultBoundedRangeModel}.
067 * <p>
068 * A <code>JSlider</code> component has the following properties:
069 * </p>
070 *
071 * <table>
072 * <tr><th> Property         </th><th> Stored in </th><th> Bound? </th></tr>
073 * <tr><td> extent           </td><td> model     </td><td> no     </td></tr>
074 * <tr><td> inverted         </td><td> slider    </td><td> yes    </td></tr>
075 * <tr><td> labelTable       </td><td> slider    </td><td> yes    </td></tr>
076 * <tr><td> majorTickSpacing </td><td> slider    </td><td> yes    </td></tr>
077 * <tr><td> maximum          </td><td> model     </td><td> yes     </td></tr>
078 * <tr><td> minimum          </td><td> model     </td><td> yes     </td></tr>
079 * <tr><td> minorTickSpacing </td><td> slider    </td><td> yes    </td></tr>
080 * <tr><td> model            </td><td> slider    </td><td> yes    </td></tr>
081 * <tr><td> orientation      </td><td> slider    </td><td> yes    </td></tr>
082 * <tr><td> paintLabels      </td><td> slider    </td><td> yes    </td></tr>
083 * <tr><td> paintTicks       </td><td> slider    </td><td> yes    </td></tr>
084 * <tr><td> snapToTicks      </td><td> slider    </td><td> yes     </td></tr>
085 * <tr><td> value            </td><td> model     </td><td> no     </td></tr>
086 * <tr><td> valueIsAdjusting </td><td> model     </td><td> no     </td></tr>
087 * </table>
088 *
089 * <p>
090 * The various behavioural aspects of these properties follows:
091 * </p>
092 *
093 * <ul>
094 * <li>
095 * When a non-bound property stored in the slider changes, the slider fires
096 * a {@link ChangeEvent} to its change listeners.
097 * </li>
098 * <li>
099 * When a bound property stored in the slider changes, the slider fires a
100 * {@link PropertyChangeEvent} to its property change listeners.
101 * </li>
102 * <li>
103 * If any of the model's properties change, it fires a {@link ChangeEvent} to
104 * its listeners, which include the slider.
105 * </li>
106 * <li>
107 * If the slider receives a {@link ChangeEvent} from its model, it will
108 * propagate the event to its own change listeners, with the event's "source"
109 * property set to refer to the slider, rather than the model.
110 * </li>
111 * </ul>
112 */
113public class JSlider extends JComponent implements SwingConstants, Accessible,
114                                                   ImageObserver,
115                                                   MenuContainer, Serializable
116{
117
118  /**
119   * A little testing shows that the reference implementation creates
120   * labels from a class named LabelUIResource.
121   */
122  private class LabelUIResource
123    extends JLabel
124    implements UIResource
125  {
126    LabelUIResource(String text, int align)
127    {
128      super(text, align);
129      setName("Slider.label");
130    }
131  }
132
133  private static final long serialVersionUID = -1441275936141218479L;
134
135  /**
136   * Provides the accessibility features for the <code>JSlider</code>
137   * component.
138   */
139  protected class AccessibleJSlider extends JComponent.AccessibleJComponent
140    implements AccessibleValue
141  {
142    private static final long serialVersionUID = -6301740148041106789L;
143
144    /**
145     * Creates a new <code>AccessibleJSlider</code> instance.
146     */
147    protected AccessibleJSlider()
148    {
149      // Nothing to do here.
150    }
151
152    /**
153     * Returns a set containing the current state of the {@link JSlider}
154     * component.
155     *
156     * @return The accessible state set.
157     */
158    public AccessibleStateSet getAccessibleStateSet()
159    {
160      AccessibleStateSet result = super.getAccessibleStateSet();
161      if (orientation == JSlider.HORIZONTAL)
162        result.add(AccessibleState.HORIZONTAL);
163      else if (orientation == JSlider.VERTICAL)
164        result.add(AccessibleState.VERTICAL);
165      return result;
166    }
167
168    /**
169     * Returns the accessible role for the <code>JSlider</code> component.
170     *
171     * @return {@link AccessibleRole#SLIDER}.
172     */
173    public AccessibleRole getAccessibleRole()
174    {
175      return AccessibleRole.SLIDER;
176    }
177
178    /**
179     * Returns an object that provides access to the current, minimum and
180     * maximum values for the {@link JSlider}.  Since this class implements
181     * {@link AccessibleValue}, it returns itself.
182     *
183     * @return The accessible value.
184     */
185    public AccessibleValue getAccessibleValue()
186    {
187      return this;
188    }
189
190    /**
191     * Returns the current value of the {@link JSlider} component, as an
192     * {@link Integer}.
193     *
194     * @return The current value of the {@link JSlider} component.
195     */
196    public Number getCurrentAccessibleValue()
197    {
198      return new Integer(getValue());
199    }
200
201    /**
202     * Sets the current value of the {@link JSlider} component and sends a
203     * {@link PropertyChangeEvent} (with the property name
204     * {@link AccessibleContext#ACCESSIBLE_VALUE_PROPERTY}) to all registered
205     * listeners.  If the supplied value is <code>null</code>, this method
206     * does nothing and returns <code>false</code>.
207     *
208     * @param value  the new slider value (<code>null</code> permitted).
209     *
210     * @return <code>true</code> if the slider value is updated, and
211     *     <code>false</code> otherwise.
212     */
213    public boolean setCurrentAccessibleValue(Number value)
214    {
215      if (value == null)
216        return false;
217      Number oldValue = getCurrentAccessibleValue();
218      setValue(value.intValue());
219      firePropertyChange(AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, oldValue,
220                         new Integer(getValue()));
221      return true;
222    }
223
224    /**
225     * Returns the minimum value of the {@link JSlider} component, as an
226     * {@link Integer}.
227     *
228     * @return The minimum value of the {@link JSlider} component.
229     */
230    public Number getMinimumAccessibleValue()
231    {
232      return new Integer(getMinimum());
233    }
234
235    /**
236     * Returns the maximum value of the {@link JSlider} component, as an
237     * {@link Integer}.
238     *
239     * @return The maximum value of the {@link JSlider} component.
240     */
241    public Number getMaximumAccessibleValue()
242    {
243      return new Integer(getMaximum());
244    }
245  }
246
247  /** Whether or not this slider paints its ticks. */
248  private transient boolean paintTicks;
249
250  /** Whether or not this slider paints its track. */
251  private transient boolean paintTrack = true;
252
253  /** Whether or not this slider paints its labels. */
254  private transient boolean paintLabels;
255
256  /**
257   * A dictionary of (Integer, Component) pairs where each Component is a
258   * JLabel and the Integer determines where the label will be painted.
259   */
260  private transient Dictionary labelTable;
261
262  /** The model used to store the slider's range and current value. */
263  protected BoundedRangeModel sliderModel;
264
265  /** The space/distance between major ticks. */
266  protected int majorTickSpacing;
267
268  /** The space/distance between minor ticks. */
269  protected int minorTickSpacing;
270
271  /** Whether the slider snaps its values to ticks. */
272  protected boolean snapToTicks;
273
274  /** The orientation (horizontal or vertical) of the slider. */
275  protected int orientation = HORIZONTAL;
276
277  /** Whether the slider is inverted. */
278  private transient boolean isInverted;
279
280  /**
281   * The listener that monitors the slider's model and forwards events to the
282   * slider's listeners (see <code>createChangeListener()</code>).
283   */
284  protected ChangeListener changeListener;
285
286  /** The change event that is passed to all listeners of this slider. */
287  protected transient ChangeEvent changeEvent;
288
289  /**
290   * Creates a new horizontal <code>JSlider</code> instance with a minimum of
291   * 0, a maximum of 100, and a value of 50.
292   */
293  public JSlider()
294  {
295    this(HORIZONTAL, 0, 100, 50);
296  }
297
298  /**
299   * Creates a new <code>JSlider</code> instance with the given orientation
300   * and a minimum of 0, a maximum of 100, and a value of 50.
301   *
302   * @param orientation The orientation of the slider ({@link #HORIZONTAL} or
303   *                    {@link #VERTICAL}).
304   *
305   * @throws IllegalArgumentException if <code>orientation</code> is not one of
306   *         the specified values.
307   */
308  public JSlider(int orientation)
309  {
310    this(orientation, 0, 100, 50);
311  }
312
313  /**
314   * Creates a new horizontal <code>JSlider</code> instance with the given
315   * maximum and minimum and a value that is halfway between the minimum and the
316   * maximum.
317   *
318   * @param minimum The minimum value.
319   * @param maximum The maximum value.
320   *
321   * @throws IllegalArgumentException if <code>minimum</code> is greater than
322   *     <code>maximum</code>.
323   */
324  public JSlider(int minimum, int maximum)
325  {
326    this(HORIZONTAL, minimum, maximum, (maximum + minimum) / 2);
327  }
328
329  /**
330   * Creates a new horizontal <code>JSlider</code> instance with the given
331   * minimum, maximum, and value.
332   *
333   * @param minimum The minimum value.
334   * @param maximum The maximum value.
335   * @param value The initial value.
336   *
337   * @throws IllegalArgumentException if <code>value</code> is not in the
338   *     specified range.
339   * @throws IllegalArgumentException if <code>minimum</code> is greater than
340   *     <code>maximum</code>.
341   */
342  public JSlider(int minimum, int maximum, int value)
343  {
344    this(HORIZONTAL, minimum, maximum, value);
345  }
346
347  /**
348   * Creates a new <code>JSlider</code> instance with the given orientation,
349   * minimum, maximum, and value.
350   *
351   * @param orientation The orientation of the slider ({@link #HORIZONTAL} or
352   *                    {@link #VERTICAL}).
353   * @param minimum The minimum value of the JSlider.
354   * @param maximum The maximum value of the JSlider.
355   * @param value The initial value of the JSlider.
356   *
357   * @throws IllegalArgumentException if <code>orientation</code> is not one of
358   *     the specified values.
359   * @throws IllegalArgumentException if <code>value</code> is not in the
360   *     specified range.
361   * @throws IllegalArgumentException if <code>minimum</code> is greater than
362   *     <code>maximum</code>.
363   */
364  public JSlider(int orientation, int minimum, int maximum, int value)
365  {
366    sliderModel = new DefaultBoundedRangeModel(value, 0, minimum, maximum);
367    if (orientation != HORIZONTAL && orientation != VERTICAL)
368      throw new IllegalArgumentException(orientation
369                                         + " is not a legal orientation");
370    this.orientation = orientation;
371    changeListener = createChangeListener();
372    sliderModel.addChangeListener(changeListener);
373    updateUI();
374  }
375
376  /**
377   * Creates a new horizontal <code>JSlider</code> instance with the given
378   * model.
379   *
380   * @param model The model (<code>null</code> not permitted).
381   *
382   * @throws NullPointerException if <code>model</code> is <code>null</code>.
383   */
384  public JSlider(BoundedRangeModel model)
385  {
386    sliderModel = model;
387    changeListener = createChangeListener();
388    sliderModel.addChangeListener(changeListener);
389    updateUI();
390  }
391
392  /**
393   * Returns the slider's value (from the slider's model).
394   *
395   * @return The value of the slider.
396   *
397   * @see #setValue(int)
398   */
399  public int getValue()
400  {
401    return sliderModel.getValue();
402  }
403
404  /**
405   * Sets the slider's value and sends a {@link ChangeEvent} to all
406   * registered listeners.  Note that the model will fire a change event to all
407   * of its registered listeners first (with the model as the event source) and
408   * then the slider will fire another change event to all of its registered
409   * listeners (this time with the slider as the event source).
410   *
411   * @param value  the new value.
412   *
413   * @see #getValue()
414   */
415  public void setValue(int value)
416  {
417    sliderModel.setValue(value);
418  }
419
420  /**
421   * Returns the slider's UI delegate.
422   *
423   * @return The slider's UI delegate.
424   */
425  public SliderUI getUI()
426  {
427    return (SliderUI) ui;
428  }
429
430  /**
431   * Sets the slider's UI delegate.
432   *
433   * @param ui  the UI delegate.
434   */
435  public void setUI(SliderUI ui)
436  {
437    super.setUI(ui);
438  }
439
440  /**
441   * Sets this slider's UI delegate to the default (obtained from the
442   * {@link UIManager}) for the current look and feel.
443   */
444  public void updateUI()
445  {
446    updateLabelUIs();
447    setUI((SliderUI) UIManager.getUI(this));
448  }
449
450  /**
451   * Returns the suffix (<code>"SliderUI"</code> in this case) used to
452   * determine the class name for a UI delegate that can provide the look and
453   * feel for a <code>JSlider</code>.
454   *
455   * @return <code>"SliderUI"</code>.
456   */
457  public String getUIClassID()
458  {
459    return "SliderUI";
460  }
461
462  /**
463   * Creates a {@link ChangeListener} that is added to the slider's model and
464   * forwards change events generated by the model to the listeners that are
465   * registered with the <code>JSlider</code> (by calling the
466   * {@link #fireStateChanged} method).
467   *
468   * @return A new listener.
469   */
470  protected ChangeListener createChangeListener()
471  {
472    return new ChangeListener()
473      {
474        public void stateChanged(ChangeEvent ce)
475        {
476          // No need to trigger a repaint since the UI listens to the model
477          // as well. All we need to do is pass on the stateChanged event
478          // to our listeners.
479          fireStateChanged();
480        }
481      };
482  }
483
484  /**
485   * Registers a listener with the slider so that it will receive
486   * {@link ChangeEvent} notifications.  Note that change events generated
487   * by the slider's model will be forwarded automatically to the slider's
488   * listeners.
489   *
490   * @param listener  the listener to register.
491   *
492   * @see #removeChangeListener(ChangeListener)
493   */
494  public void addChangeListener(ChangeListener listener)
495  {
496    listenerList.add(ChangeListener.class, listener);
497  }
498
499  /**
500   * Removes a listener from this slider so that it will no longer receive
501   * {@link ChangeEvent} notifications from the slider.
502   *
503   * @param listener The listener to remove.
504   *
505   * @see #addChangeListener(ChangeListener)
506   */
507  public void removeChangeListener(ChangeListener listener)
508  {
509    listenerList.remove(ChangeListener.class, listener);
510  }
511
512  /**
513   * Sends a {@link ChangeEvent} to all registered listeners, with this slider
514   * as the source.
515   */
516  protected void fireStateChanged()
517  {
518    Object[] changeListeners = listenerList.getListenerList();
519    if (changeEvent == null)
520      changeEvent = new ChangeEvent(this);
521    for (int i = changeListeners.length - 2; i >= 0; i -= 2)
522      {
523        if (changeListeners[i] == ChangeListener.class)
524          ((ChangeListener) changeListeners[i + 1]).stateChanged(changeEvent);
525      }
526  }
527
528  /**
529   * Returns an array containing all the {@link ChangeListener} instances
530   * registered with this slider.  If no listeners are registered, this method
531   * returns an empty array.
532   *
533   * @return An array array containing all the {@link ChangeListener} instances
534   *     registered with this slider (possibly empty, but never
535   *     <code>null</code>).
536   */
537  public ChangeListener[] getChangeListeners()
538  {
539    return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
540  }
541
542  /**
543   * Returns the slider's model, which stores the minimum, maximum and current
544   * values.
545   *
546   * @return The slider's model.
547   *
548   * @see #setModel(BoundedRangeModel)
549   */
550  public BoundedRangeModel getModel()
551  {
552    return sliderModel;
553  }
554
555  /**
556   * Sets the slider's model and sends a {@link PropertyChangeEvent} (with the
557   * property name "model") to all registered listeners.   The change listener
558   * that the slider registered with the original model is removed and added
559   * to the new model (this ensures that {@link ChangeEvent} notifications
560   * generated by the model are automatically forwarded to listeners that are
561   * registered with the slider).
562   *
563   * @param model The model to use with the slider.
564   *
565   * @see #getModel()
566   */
567  public void setModel(BoundedRangeModel model)
568  {
569    // I didn't do the null pointer check on purpose.
570    // If you try it with Sun's, it'll go ahead and set it to null
571    // and bork the next time it tries to access the model.
572    if (model != sliderModel)
573      {
574        BoundedRangeModel oldModel = sliderModel;
575        sliderModel = model;
576        oldModel.removeChangeListener(changeListener);
577        sliderModel.addChangeListener(changeListener);
578        firePropertyChange("model", oldModel, sliderModel);
579      }
580  }
581
582  /**
583   * Returns the minimum value of the slider (from the slider's model).
584   *
585   * @return The minimum value of the slider.
586   *
587   * @see #setMinimum(int)
588   */
589  public int getMinimum()
590  {
591    return sliderModel.getMinimum();
592  }
593
594  /**
595   * Sets the minimum value of the slider and fires a
596   * {@link PropertyChangeEvent} (with the property name "minimum") to all
597   * registered listeners.  Note that:
598   * <p>
599   * <ul>
600   * <li>the minimum value is stored in the slider's model (see
601   *     {@link #getModel()});</li>
602   * <li>in addition to the property change event, the slider also fires a
603   *     {@link ChangeEvent}.</li>
604   * </ul>
605   *
606   * @param minimum The minimum value of the slider.
607   *
608   * @see #getMinimum()
609   */
610  public void setMinimum(int minimum)
611  {
612    int old = sliderModel.getMinimum();
613    sliderModel.setMinimum(minimum);
614    if (minimum != old)
615      firePropertyChange("minimum", old, minimum);
616  }
617
618  /**
619   * Returns the slider's maximum value (obtained from the slider's model).
620   *
621   * @return The maximum value of the slider.
622   *
623   * @see #setMaximum(int)
624   */
625  public int getMaximum()
626  {
627    return sliderModel.getMaximum();
628  }
629
630  /**
631   * Sets the maximum value of the slider and fires a
632   * {@link PropertyChangeEvent} (with the property name "maximum") to all
633   * registered listeners.  Note that:
634   * <p>
635   * <ul>
636   * <li>the maximum value is stored in the slider's model (see
637   *     {@link #getModel()});</li>
638   * <li>in addition to the property change event, the slider also fires a
639   *     {@link ChangeEvent}.</li>
640   * </ul>
641   *
642   * @param maximum The maximum value of the slider.
643   *
644   * @see #getMaximum()
645   */
646  public void setMaximum(int maximum)
647  {
648    int old = sliderModel.getMaximum();
649    sliderModel.setMaximum(maximum);
650    if (maximum != old)
651      firePropertyChange("maximum", old, maximum);
652  }
653
654  /**
655   * Returns the <code>valueIsAdjusting</code> flag from the slider's model.
656   *
657   * @return The <code>valueIsAdjusting</code> flag from the slider's model.
658   *
659   * @see #setValueIsAdjusting(boolean)
660   */
661  public boolean getValueIsAdjusting()
662  {
663    return sliderModel.getValueIsAdjusting();
664  }
665
666  /**
667   * Sets the <code>valueIsAdjusting</code> flag in the slider's model, and
668   * sends a {@link ChangeEvent} to all registered listeners.
669   *
670   * @param adjusting  the new flag value.
671   *
672   * @see #getValueIsAdjusting()
673   */
674  public void setValueIsAdjusting(boolean adjusting)
675  {
676    sliderModel.setValueIsAdjusting(adjusting);
677  }
678
679  /**
680   * Returns the slider's extent value, obtained from the slider's model.
681   *
682   * @return The extent value.
683   *
684   * @see #setExtent(int)
685   */
686  public int getExtent()
687  {
688    return sliderModel.getExtent();
689  }
690
691  /**
692   * Sets the slider's extent value and sends a {@link ChangeEvent} to all
693   * registered listeners.  Note that the model will fire a change event to all
694   * of its registered listeners first (with the model as the event source) and
695   * then the slider will fire another change event to all of its registered
696   * listeners (this time with the slider as the event source).
697   *
698   * @param extent The extent value for this slider.
699   *
700   * @see #getExtent()
701   */
702  public void setExtent(int extent)
703  {
704    sliderModel.setExtent(extent);
705  }
706
707  /**
708   * Returns the orientation of the slider, either {@link JSlider#HORIZONTAL}
709   * or {@link JSlider#VERTICAL}.
710   *
711   * @return The orientation of the slider.
712   *
713   * @see #setOrientation(int)
714   */
715  public int getOrientation()
716  {
717    return orientation;
718  }
719
720  /**
721   * Sets the orientation for the slider and sends a
722   * {@link PropertyChangeEvent} (with the property name "orientation") to all
723   * registered listeners.
724   *
725   * @param orientation  the orientation (one of {@link JSlider#HORIZONTAL} or
726   *     {@link JSlider#VERTICAL}).
727   *
728   * @throws IllegalArgumentException if <code>orientation</code> is not one of
729   *     the permitted values.
730   *
731   * @see #getOrientation()
732   */
733  public void setOrientation(int orientation)
734  {
735    if (orientation != VERTICAL && orientation != HORIZONTAL)
736      throw new IllegalArgumentException(
737          "orientation must be one of: VERTICAL, HORIZONTAL");
738    if (orientation != this.orientation)
739      {
740        int oldOrientation = this.orientation;
741        this.orientation = orientation;
742        firePropertyChange("orientation", oldOrientation, this.orientation);
743        revalidate();
744      }
745  }
746
747  /**
748   * Returns the label table for the slider.
749   *
750   * @return The label table for the slider (possibly <code>null</code>).
751   *
752   * @see #setLabelTable(Dictionary)
753   */
754  public Dictionary getLabelTable()
755  {
756    return labelTable;
757  }
758
759  /**
760   * Sets the table of labels for the slider and sends a
761   * {@link PropertyChangeEvent} (with the property name "labelTable") to all
762   * registered listeners.
763   *
764   * @param table  the table of labels (<code>null</code> permitted).
765   *
766   * @see #getLabelTable()
767   */
768  public void setLabelTable(Dictionary table)
769  {
770    if (table != labelTable)
771      {
772        Dictionary oldTable = labelTable;
773        labelTable = table;
774        updateLabelUIs();
775        firePropertyChange("labelTable", oldTable, labelTable);
776        revalidate();
777        repaint();
778      }
779  }
780
781  /**
782   * Resets the UI delegates for the labels in the <code>labelTable</code> to
783   * the default for the current look and feel.
784   */
785  protected void updateLabelUIs()
786  {
787    if (labelTable != null)
788      {
789        for (Enumeration list = labelTable.elements(); list.hasMoreElements();)
790          {
791            Object o = list.nextElement();
792            if (o instanceof JComponent)
793              {
794                JComponent jc = (JComponent) o;
795                jc.updateUI();
796                jc.setSize(jc.getPreferredSize());
797              }
798          }
799      }
800  }
801
802  /**
803   * Creates a hashtable of <code>(Integer, JLabel)</code> pairs that can be
804   * used as a label table for this slider. The labels will start from the
805   * slider's minimum and increase by the increment. Each label will have a text
806   * string indicating its integer value.
807   *
808   * @param increment The increment between labels (must be > 0).
809   *
810   * @return A hashtable containing the labels.
811   *
812   * @throws IllegalArgumentException if <code>increment</code> is not greater
813   *         than zero.
814   */
815  public Hashtable createStandardLabels(int increment)
816  {
817    return createStandardLabels(increment, sliderModel.getMinimum());
818  }
819
820  /**
821   * Creates a hashtable of <code>(Integer, JLabel)</code> pairs that can be
822   * used as a label table for this slider. The labels will start from the
823   * given start value and increase by the increment. Each  label will have a
824   * text string indicating its integer value.
825   *
826   * @param increment The increment between labels (must be > 0).
827   * @param start The value to start from.
828   *
829   * @return A hashtable with the labels and their keys.
830   *
831   * @throws IllegalArgumentException if <code>increment</code> is not greater
832   *         than zero, or <code>start</code> is not within the range of the
833   *         model.
834   */
835  public Hashtable createStandardLabels(int increment, int start)
836  {
837    if (increment <= 0)
838      throw new IllegalArgumentException("Requires 'increment' > 0.");
839    if (start < getMinimum() || start > getMaximum())
840      throw new IllegalArgumentException("The 'start' value is out of range.");
841    Hashtable table = new Hashtable();
842    int max = getMaximum();
843    for (int i = start; i <= max; i += increment)
844      {
845        LabelUIResource label = new LabelUIResource(String.valueOf(i),
846                                                    JLabel.CENTER);
847        table.put(new Integer(i), label);
848      }
849    return table;
850  }
851
852  /**
853   * Returns the flag that controls whether or not the value scale for the
854   * slider is inverted (the default value is <code>false</code>).
855   *
856   * @return The flag that controls whether or not the value scale for the
857   *     slider is inverted.
858   *
859   * @see #setInverted(boolean)
860   */
861  public boolean getInverted()
862  {
863    return isInverted;
864  }
865
866  /**
867   * Sets the flag that controls whether or not the value scale for the
868   * slider is inverted and, if the new flag value is different to the old flag
869   * value, sends a {@link PropertyChangeEvent} to all registered listeners.
870   * Typically, a horizontal slider will display a scale that increases from
871   * left to right, but this is reversed if the 'inverted' flag is set to
872   * <code>true</code>.  Similarly, a vertical slider will display a scale that
873   * increases from bottom to top, and this is reversed if the 'inverted' flag
874   * is set to <code>true</code>.
875   *
876   * @param inverted  the new flag value.
877   *
878   * @see #getInverted()
879   */
880  public void setInverted(boolean inverted)
881  {
882    if (isInverted != inverted)
883      {
884        boolean oldInverted = isInverted;
885        isInverted = inverted;
886        firePropertyChange("inverted", oldInverted, isInverted);
887        repaint();
888      }
889  }
890
891  /**
892   * Returns the distance between major tick marks along the slider's value
893   * scale.
894   *
895   * @return The amount of units between each major tick mark.
896   *
897   * @see #setMajorTickSpacing(int)
898   */
899  public int getMajorTickSpacing()
900  {
901    return majorTickSpacing;
902  }
903
904  /**
905   * Sets the distance between major tick marks along the slider's value scale,
906   * and sends a {@link PropertyChangeEvent} (with the property name
907   * "majorTickSpacing") to all registered listeners.
908   *
909   * @param spacing  the distance between major tick marks.
910   *
911   * @see #getMajorTickSpacing()
912   */
913  public void setMajorTickSpacing(int spacing)
914  {
915    if (majorTickSpacing != spacing)
916      {
917        int oldSpacing = majorTickSpacing;
918        majorTickSpacing = spacing;
919        if (labelTable == null && majorTickSpacing > 0 && getPaintLabels())
920          setLabelTable(createStandardLabels(majorTickSpacing));
921        firePropertyChange("majorTickSpacing", oldSpacing, majorTickSpacing);
922        if (getPaintTicks())
923          repaint();
924      }
925  }
926
927  /**
928   * Returns the distance between minor tick marks along the slider's value
929   * scale.
930   *
931   * @return The distance between minor tick marks along the slider's value
932   *     scale.
933   *
934   * @see #setMinorTickSpacing(int)
935   */
936  public int getMinorTickSpacing()
937  {
938    return minorTickSpacing;
939  }
940
941  /**
942   * Sets the distance between minor tick marks along the slider's value scale,
943   * and sends a {@link PropertyChangeEvent} (with the property name
944   * "minorTickSpacing") to all registered listeners.
945   *
946   * @param spacing  the distance between minor tick marks.
947   *
948   * @see #getMinorTickSpacing()
949   */
950  public void setMinorTickSpacing(int spacing)
951  {
952    if (minorTickSpacing != spacing)
953      {
954        int oldSpacing = minorTickSpacing;
955        minorTickSpacing = spacing;
956        firePropertyChange("minorTickSpacing", oldSpacing, minorTickSpacing);
957        if (getPaintTicks())
958          repaint();
959      }
960  }
961
962  /**
963   * Returns the flag that controls whether the slider thumb will snap to ticks.
964   * Sliders that snap to ticks will automatically move the thumb to the
965   * nearest tick mark.
966   *
967   * @return <code>true</code> if the slider thumb automatically.
968   *
969   * @see #setSnapToTicks(boolean)
970   */
971  public boolean getSnapToTicks()
972  {
973    return snapToTicks;
974  }
975
976  /**
977   * Sets the flag that controls whether the slider thumb will snap to ticks
978   * and sends a {@link PropertyChangeEvent} (with the property name
979   * 'snapToTicks') to all registered listeners. Sliders that snap to ticks
980   * will automatically move the thumb to the nearest tick mark.
981   *
982   * @param snap  the new flag value.
983   *
984   * @see #getSnapToTicks()
985   */
986  public void setSnapToTicks(boolean snap)
987  {
988    if (snap != snapToTicks)
989      {
990        snapToTicks = snap;
991        firePropertyChange("snapToTicks", !snap, snap);
992      }
993  }
994
995  /**
996   * Returns the flag that controls whether or not tick marks are painted along
997   * the slider's value scale.
998   *
999   * @return <code>true</code> if tick marks should be painted, and
1000   *     <code>false</code> if tick marks should not be painted.
1001   *
1002   * @see #setPaintTicks(boolean)
1003   */
1004  public boolean getPaintTicks()
1005  {
1006    return paintTicks;
1007  }
1008
1009  /**
1010   * Sets the flag that controls whether or not tick marks are painted along
1011   * the slider's value scale, and sends a {@link PropertyChangeEvent} (with
1012   * the property name "paintTicks") to all registered listeners. In
1013   * addition to setting this property to <code>true</code>, one or both of the
1014   * minor tick spacing and major tick spacing attributes must be set to a
1015   * value greater than 0 in order for ticks to be painted.
1016   *
1017   * @param paint Whether ticks will be painted.
1018   *
1019   * @see #getPaintTicks()
1020   */
1021  public void setPaintTicks(boolean paint)
1022  {
1023    if (paint != paintTicks)
1024      {
1025        boolean oldPaintTicks = paintTicks;
1026        paintTicks = paint;
1027        firePropertyChange("paintTicks", oldPaintTicks, paintTicks);
1028        revalidate();
1029        repaint();
1030      }
1031  }
1032
1033  /**
1034   * Returns the flag that controls whether or not the track is painted.
1035   *
1036   * @return Whether the track will be painted.
1037   *
1038   * @see #setPaintTrack(boolean)
1039   */
1040  public boolean getPaintTrack()
1041  {
1042    return paintTrack;
1043  }
1044
1045  /**
1046   * Sets the flag that controls whether or not the track is painted, and
1047   * sends a {@link PropertyChangeEvent} (for the "paintTrack" property) to all
1048   * registered listeners.
1049   *
1050   * @param paint Whether the track will be painted.
1051   *
1052   * @see #getPaintTrack()
1053   */
1054  public void setPaintTrack(boolean paint)
1055  {
1056    if (paintTrack != paint)
1057    {
1058      paintTrack = paint;
1059      firePropertyChange("paintTrack", !paint, paint);
1060      repaint();
1061    }
1062  }
1063
1064  /**
1065   * Returns the flag that controls whether or not labels are painted for the
1066   * tick marks along the slider.
1067   *
1068   * @return Whether labels will be painted.
1069   *
1070   * @see #setPaintLabels(boolean)
1071   */
1072  public boolean getPaintLabels()
1073  {
1074    return paintLabels;
1075  }
1076
1077  /**
1078   * Sets the flag that controls whether or not labels are painted for the
1079   * tick marks along the slider and sends a {@link PropertyChangeEvent} (with
1080   * the property name "paintLabels") to all registered listeners.
1081   *
1082   * @param paint Whether labels will be painted.
1083   *
1084   * @see #getPaintLabels()
1085   */
1086  public void setPaintLabels(boolean paint)
1087  {
1088    if (paint != paintLabels)
1089      {
1090        paintLabels = paint;
1091        if (paint && majorTickSpacing > 0 && labelTable == null)
1092          setLabelTable(createStandardLabels(majorTickSpacing));
1093        firePropertyChange("paintLabels", !paint, paint);
1094        revalidate();
1095        repaint();
1096      }
1097  }
1098
1099  /**
1100   * Returns an implementation-dependent string describing the attributes of
1101   * this <code>JSlider</code>.
1102   *
1103   * @return A string describing the attributes of this <code>JSlider</code>
1104   *         (never <code>null</code>).
1105   */
1106  protected String paramString()
1107  {
1108    String superParamStr = super.paramString();
1109    CPStringBuilder sb = new CPStringBuilder();
1110    sb.append(",isInverted=").append(getInverted());
1111    sb.append(",majorTickSpacing=").append(getMajorTickSpacing());
1112    sb.append(",minorTickSpacing=").append(getMinorTickSpacing());
1113    sb.append(",orientation=");
1114    if (orientation == HORIZONTAL)
1115      sb.append("HORIZONTAL");
1116    else
1117      sb.append("VERTICAL");
1118    sb.append(",paintLabels=").append(getPaintLabels());
1119    sb.append(",paintTicks=").append(getPaintTicks());
1120    sb.append(",paintTrack=").append(getPaintTrack());
1121    sb.append(",snapToTicks=").append(getSnapToTicks());
1122
1123    // the following is output by the reference implementation.  We don't
1124    // strictly need to replicate this. Perhaps it has some meaning, but
1125    // I couldn't determine it yet...
1126    sb.append(",snapToValue=true");
1127
1128    return superParamStr + sb.toString();
1129  }
1130
1131  /**
1132   * Returns the object that provides accessibility features for this
1133   * <code>JSlider</code> component.
1134   *
1135   * @return The accessible context (an instance of {@link AccessibleJSlider}).
1136   */
1137  public AccessibleContext getAccessibleContext()
1138  {
1139    if (accessibleContext == null)
1140      accessibleContext = new AccessibleJSlider();
1141
1142    return accessibleContext;
1143  }
1144}