JSlider.java 34.5 KB
Newer Older
Tom Tromey committed
1
/* JSlider.java --
2
   Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
Tom Tromey committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing;

41 42
import gnu.java.lang.CPStringBuilder;

Tom Tromey committed
43 44
import java.awt.MenuContainer;
import java.awt.image.ImageObserver;
Tom Tromey committed
45
import java.beans.PropertyChangeEvent;
Tom Tromey committed
46 47 48 49 50 51 52 53
import java.io.Serializable;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
54
import javax.accessibility.AccessibleState;
Tom Tromey committed
55 56 57 58 59
import javax.accessibility.AccessibleStateSet;
import javax.accessibility.AccessibleValue;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.SliderUI;
60
import javax.swing.plaf.UIResource;
Tom Tromey committed
61 62

/**
63
 * A visual component that allows selection of a value within a
Tom Tromey committed
64 65 66 67
 * range by adjusting a thumb in a track. The values for the minimum,
 * maximum, extent and value are stored in a {@link
 * DefaultBoundedRangeModel}.
 * <p>
68
 * A <code>JSlider</code> component has the following properties:
Tom Tromey committed
69
 * </p>
70
 *
Tom Tromey committed
71 72 73 74 75
 * <table>
 * <tr><th> Property         </th><th> Stored in </th><th> Bound? </th></tr>
 * <tr><td> extent           </td><td> model     </td><td> no     </td></tr>
 * <tr><td> inverted         </td><td> slider    </td><td> yes    </td></tr>
 * <tr><td> labelTable       </td><td> slider    </td><td> yes    </td></tr>
76
 * <tr><td> majorTickSpacing </td><td> slider    </td><td> yes    </td></tr>
77 78
 * <tr><td> maximum          </td><td> model     </td><td> yes     </td></tr>
 * <tr><td> minimum          </td><td> model     </td><td> yes     </td></tr>
Tom Tromey committed
79
 * <tr><td> minorTickSpacing </td><td> slider    </td><td> yes    </td></tr>
80
 * <tr><td> model            </td><td> slider    </td><td> yes    </td></tr>
Tom Tromey committed
81 82 83
 * <tr><td> orientation      </td><td> slider    </td><td> yes    </td></tr>
 * <tr><td> paintLabels      </td><td> slider    </td><td> yes    </td></tr>
 * <tr><td> paintTicks       </td><td> slider    </td><td> yes    </td></tr>
84
 * <tr><td> snapToTicks      </td><td> slider    </td><td> yes     </td></tr>
Tom Tromey committed
85 86 87
 * <tr><td> value            </td><td> model     </td><td> no     </td></tr>
 * <tr><td> valueIsAdjusting </td><td> model     </td><td> no     </td></tr>
 * </table>
88
 *
Tom Tromey committed
89
 * <p>
90
 * The various behavioural aspects of these properties follows:
Tom Tromey committed
91
 * </p>
92
 *
Tom Tromey committed
93 94
 * <ul>
 * <li>
95 96
 * When a non-bound property stored in the slider changes, the slider fires
 * a {@link ChangeEvent} to its change listeners.
Tom Tromey committed
97 98
 * </li>
 * <li>
99 100
 * When a bound property stored in the slider changes, the slider fires a
 * {@link PropertyChangeEvent} to its property change listeners.
Tom Tromey committed
101 102
 * </li>
 * <li>
103
 * If any of the model's properties change, it fires a {@link ChangeEvent} to
104
 * its listeners, which include the slider.
Tom Tromey committed
105 106
 * </li>
 * <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"
Tom Tromey committed
109 110 111 112 113 114 115 116
 * property set to refer to the slider, rather than the model.
 * </li>
 * </ul>
 */
public class JSlider extends JComponent implements SwingConstants, Accessible,
                                                   ImageObserver,
                                                   MenuContainer, Serializable
{
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132

  /**
   * A little testing shows that the reference implementation creates
   * labels from a class named LabelUIResource.
   */
  private class LabelUIResource
    extends JLabel
    implements UIResource
  {
    LabelUIResource(String text, int align)
    {
      super(text, align);
      setName("Slider.label");
    }
  }

Tom Tromey committed
133 134 135
  private static final long serialVersionUID = -1441275936141218479L;

  /**
136 137
   * Provides the accessibility features for the <code>JSlider</code>
   * component.
Tom Tromey committed
138 139 140 141 142
   */
  protected class AccessibleJSlider extends JComponent.AccessibleJComponent
    implements AccessibleValue
  {
    private static final long serialVersionUID = -6301740148041106789L;
143

Tom Tromey committed
144
    /**
145
     * Creates a new <code>AccessibleJSlider</code> instance.
Tom Tromey committed
146 147 148
     */
    protected AccessibleJSlider()
    {
149
      // Nothing to do here.
Tom Tromey committed
150 151 152
    }

    /**
153
     * Returns a set containing the current state of the {@link JSlider}
154
     * component.
Tom Tromey committed
155
     *
156
     * @return The accessible state set.
Tom Tromey committed
157 158 159
     */
    public AccessibleStateSet getAccessibleStateSet()
    {
160 161 162 163 164 165
      AccessibleStateSet result = super.getAccessibleStateSet();
      if (orientation == JSlider.HORIZONTAL)
        result.add(AccessibleState.HORIZONTAL);
      else if (orientation == JSlider.VERTICAL)
        result.add(AccessibleState.VERTICAL);
      return result;
Tom Tromey committed
166 167 168
    }

    /**
169
     * Returns the accessible role for the <code>JSlider</code> component.
Tom Tromey committed
170
     *
171
     * @return {@link AccessibleRole#SLIDER}.
Tom Tromey committed
172 173 174
     */
    public AccessibleRole getAccessibleRole()
    {
175
      return AccessibleRole.SLIDER;
Tom Tromey committed
176 177 178
    }

    /**
179 180
     * Returns an object that provides access to the current, minimum and
     * maximum values for the {@link JSlider}.  Since this class implements
181
     * {@link AccessibleValue}, it returns itself.
Tom Tromey committed
182
     *
183
     * @return The accessible value.
Tom Tromey committed
184 185 186
     */
    public AccessibleValue getAccessibleValue()
    {
187
      return this;
Tom Tromey committed
188 189 190
    }

    /**
191 192
     * Returns the current value of the {@link JSlider} component, as an
     * {@link Integer}.
Tom Tromey committed
193
     *
194
     * @return The current value of the {@link JSlider} component.
Tom Tromey committed
195 196 197
     */
    public Number getCurrentAccessibleValue()
    {
198
      return new Integer(getValue());
Tom Tromey committed
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>.
Tom Tromey committed
207
     *
208
     * @param value  the new slider value (<code>null</code> permitted).
Tom Tromey committed
209
     *
210
     * @return <code>true</code> if the slider value is updated, and
211
     *     <code>false</code> otherwise.
Tom Tromey committed
212
     */
213
    public boolean setCurrentAccessibleValue(Number value)
Tom Tromey committed
214
    {
215 216 217 218
      if (value == null)
        return false;
      Number oldValue = getCurrentAccessibleValue();
      setValue(value.intValue());
219
      firePropertyChange(AccessibleContext.ACCESSIBLE_VALUE_PROPERTY, oldValue,
220 221
                         new Integer(getValue()));
      return true;
Tom Tromey committed
222 223 224
    }

    /**
225 226
     * Returns the minimum value of the {@link JSlider} component, as an
     * {@link Integer}.
Tom Tromey committed
227
     *
228
     * @return The minimum value of the {@link JSlider} component.
Tom Tromey committed
229 230 231
     */
    public Number getMinimumAccessibleValue()
    {
232
      return new Integer(getMinimum());
Tom Tromey committed
233 234 235
    }

    /**
236 237
     * Returns the maximum value of the {@link JSlider} component, as an
     * {@link Integer}.
Tom Tromey committed
238
     *
239
     * @return The maximum value of the {@link JSlider} component.
Tom Tromey committed
240 241 242
     */
    public Number getMaximumAccessibleValue()
    {
243
      return new Integer(getMaximum());
Tom Tromey committed
244 245 246 247
    }
  }

  /** Whether or not this slider paints its ticks. */
248
  private transient boolean paintTicks;
Tom Tromey committed
249 250 251 252 253

  /** Whether or not this slider paints its track. */
  private transient boolean paintTrack = true;

  /** Whether or not this slider paints its labels. */
254
  private transient boolean paintLabels;
Tom Tromey committed
255 256 257 258 259 260 261

  /**
   * A dictionary of (Integer, Component) pairs where each Component is a
   * JLabel and the Integer determines where the label will be painted.
   */
  private transient Dictionary labelTable;

262
  /** The model used to store the slider's range and current value. */
Tom Tromey committed
263 264
  protected BoundedRangeModel sliderModel;

265
  /** The space/distance between major ticks. */
Tom Tromey committed
266 267
  protected int majorTickSpacing;

268
  /** The space/distance between minor ticks. */
Tom Tromey committed
269 270 271
  protected int minorTickSpacing;

  /** Whether the slider snaps its values to ticks. */
272
  protected boolean snapToTicks;
Tom Tromey committed
273

274
  /** The orientation (horizontal or vertical) of the slider. */
Tom Tromey committed
275 276 277 278 279
  protected int orientation = HORIZONTAL;

  /** Whether the slider is inverted. */
  private transient boolean isInverted;

280
  /**
281
   * The listener that monitors the slider's model and forwards events to the
282
   * slider's listeners (see <code>createChangeListener()</code>).
283
   */
Tom Tromey committed
284 285
  protected ChangeListener changeListener;

286
  /** The change event that is passed to all listeners of this slider. */
Tom Tromey committed
287 288 289
  protected transient ChangeEvent changeEvent;

  /**
290
   * Creates a new horizontal <code>JSlider</code> instance with a minimum of
291
   * 0, a maximum of 100, and a value of 50.
Tom Tromey committed
292 293 294 295 296 297 298
   */
  public JSlider()
  {
    this(HORIZONTAL, 0, 100, 50);
  }

  /**
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.
Tom Tromey committed
301
   *
Tom Tromey committed
302 303
   * @param orientation The orientation of the slider ({@link #HORIZONTAL} or
   *                    {@link #VERTICAL}).
304
   *
Tom Tromey committed
305 306
   * @throws IllegalArgumentException if <code>orientation</code> is not one of
   *         the specified values.
Tom Tromey committed
307 308 309 310 311 312 313
   */
  public JSlider(int orientation)
  {
    this(orientation, 0, 100, 50);
  }

  /**
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
Tom Tromey committed
316 317
   * maximum.
   *
318 319
   * @param minimum The minimum value.
   * @param maximum The maximum value.
320
   *
321 322
   * @throws IllegalArgumentException if <code>minimum</code> is greater than
   *     <code>maximum</code>.
Tom Tromey committed
323 324 325 326 327 328 329
   */
  public JSlider(int minimum, int maximum)
  {
    this(HORIZONTAL, minimum, maximum, (maximum + minimum) / 2);
  }

  /**
330
   * Creates a new horizontal <code>JSlider</code> instance with the given
331
   * minimum, maximum, and value.
Tom Tromey committed
332
   *
333 334 335
   * @param minimum The minimum value.
   * @param maximum The maximum value.
   * @param value The initial value.
336 337
   *
   * @throws IllegalArgumentException if <code>value</code> is not in the
338 339 340
   *     specified range.
   * @throws IllegalArgumentException if <code>minimum</code> is greater than
   *     <code>maximum</code>.
Tom Tromey committed
341 342 343 344 345 346 347
   */
  public JSlider(int minimum, int maximum, int value)
  {
    this(HORIZONTAL, minimum, maximum, value);
  }

  /**
348
   * Creates a new <code>JSlider</code> instance with the given orientation,
349
   * minimum, maximum, and value.
Tom Tromey committed
350
   *
Tom Tromey committed
351 352
   * @param orientation The orientation of the slider ({@link #HORIZONTAL} or
   *                    {@link #VERTICAL}).
Tom Tromey committed
353 354 355
   * @param minimum The minimum value of the JSlider.
   * @param maximum The maximum value of the JSlider.
   * @param value The initial value of the JSlider.
356
   *
Tom Tromey committed
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 361 362
   *     specified range.
   * @throws IllegalArgumentException if <code>minimum</code> is greater than
   *     <code>maximum</code>.
Tom Tromey committed
363 364 365 366 367
   */
  public JSlider(int orientation, int minimum, int maximum, int value)
  {
    sliderModel = new DefaultBoundedRangeModel(value, 0, minimum, maximum);
    if (orientation != HORIZONTAL && orientation != VERTICAL)
368
      throw new IllegalArgumentException(orientation
369
                                         + " is not a legal orientation");
Tom Tromey committed
370 371 372 373 374 375 376
    this.orientation = orientation;
    changeListener = createChangeListener();
    sliderModel.addChangeListener(changeListener);
    updateUI();
  }

  /**
377
   * Creates a new horizontal <code>JSlider</code> instance with the given
378
   * model.
Tom Tromey committed
379
   *
Tom Tromey committed
380
   * @param model The model (<code>null</code> not permitted).
381
   *
Tom Tromey committed
382
   * @throws NullPointerException if <code>model</code> is <code>null</code>.
Tom Tromey committed
383 384 385
   */
  public JSlider(BoundedRangeModel model)
  {
Tom Tromey committed
386
    sliderModel = model;
Tom Tromey committed
387 388 389 390 391 392
    changeListener = createChangeListener();
    sliderModel.addChangeListener(changeListener);
    updateUI();
  }

  /**
393
   * Returns the slider's value (from the slider's model).
Tom Tromey committed
394
   *
395
   * @return The value of the slider.
396
   *
397
   * @see #setValue(int)
Tom Tromey committed
398 399 400 401 402 403 404
   */
  public int getValue()
  {
    return sliderModel.getValue();
  }

  /**
405
   * Sets the slider's value and sends a {@link ChangeEvent} to all
406 407 408 409
   * registered listeners.  Note that the model will fire a change event to all
   * of its registered listeners first (with the model as the event source) and
   * then the slider will fire another change event to all of its registered
   * listeners (this time with the slider as the event source).
Tom Tromey committed
410
   *
411
   * @param value  the new value.
412
   *
413
   * @see #getValue()
Tom Tromey committed
414 415 416 417 418 419 420
   */
  public void setValue(int value)
  {
    sliderModel.setValue(value);
  }

  /**
421
   * Returns the slider's UI delegate.
Tom Tromey committed
422 423 424 425 426 427 428 429 430
   *
   * @return The slider's UI delegate.
   */
  public SliderUI getUI()
  {
    return (SliderUI) ui;
  }

  /**
431
   * Sets the slider's UI delegate.
Tom Tromey committed
432
   *
433
   * @param ui  the UI delegate.
Tom Tromey committed
434 435 436 437 438 439 440
   */
  public void setUI(SliderUI ui)
  {
    super.setUI(ui);
  }

  /**
441 442
   * Sets this slider's UI delegate to the default (obtained from the
   * {@link UIManager}) for the current look and feel.
Tom Tromey committed
443 444 445
   */
  public void updateUI()
  {
446
    updateLabelUIs();
Tom Tromey committed
447 448 449 450
    setUI((SliderUI) UIManager.getUI(this));
  }

  /**
451 452
   * Returns the suffix (<code>"SliderUI"</code> in this case) used to
   * determine the class name for a UI delegate that can provide the look and
453
   * feel for a <code>JSlider</code>.
Tom Tromey committed
454
   *
455
   * @return <code>"SliderUI"</code>.
Tom Tromey committed
456 457 458 459 460 461 462
   */
  public String getUIClassID()
  {
    return "SliderUI";
  }

  /**
463 464
   * Creates a {@link ChangeListener} that is added to the slider's model and
   * 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).
Tom Tromey committed
467
   *
468
   * @return A new listener.
Tom Tromey committed
469 470 471 472 473
   */
  protected ChangeListener createChangeListener()
  {
    return new ChangeListener()
      {
474 475 476
        public void stateChanged(ChangeEvent ce)
        {
          // 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 479 480
          // to our listeners.
          fireStateChanged();
        }
Tom Tromey committed
481 482 483 484
      };
  }

  /**
485
   * Registers a listener with the slider so that it will receive
486 487 488
   * {@link ChangeEvent} notifications.  Note that change events generated
   * by the slider's model will be forwarded automatically to the slider's
   * listeners.
Tom Tromey committed
489
   *
490
   * @param listener  the listener to register.
491
   *
492
   * @see #removeChangeListener(ChangeListener)
Tom Tromey committed
493 494 495 496 497 498 499
   */
  public void addChangeListener(ChangeListener listener)
  {
    listenerList.add(ChangeListener.class, listener);
  }

  /**
500 501
   * Removes a listener from this slider so that it will no longer receive
   * {@link ChangeEvent} notifications from the slider.
Tom Tromey committed
502 503
   *
   * @param listener The listener to remove.
504
   *
505
   * @see #addChangeListener(ChangeListener)
Tom Tromey committed
506 507 508 509 510 511 512
   */
  public void removeChangeListener(ChangeListener listener)
  {
    listenerList.remove(ChangeListener.class, listener);
  }

  /**
513
   * Sends a {@link ChangeEvent} to all registered listeners, with this slider
514
   * as the source.
Tom Tromey committed
515 516 517 518 519 520 521 522
   */
  protected void fireStateChanged()
  {
    Object[] changeListeners = listenerList.getListenerList();
    if (changeEvent == null)
      changeEvent = new ChangeEvent(this);
    for (int i = changeListeners.length - 2; i >= 0; i -= 2)
      {
523 524
        if (changeListeners[i] == ChangeListener.class)
          ((ChangeListener) changeListeners[i + 1]).stateChanged(changeEvent);
Tom Tromey committed
525 526 527 528
      }
  }

  /**
529
   * Returns an array containing all the {@link ChangeListener} instances
530 531
   * registered with this slider.  If no listeners are registered, this method
   * returns an empty array.
Tom Tromey committed
532
   *
533 534
   * @return An array array containing all the {@link ChangeListener} instances
   *     registered with this slider (possibly empty, but never
535
   *     <code>null</code>).
Tom Tromey committed
536 537 538 539 540 541 542
   */
  public ChangeListener[] getChangeListeners()
  {
    return (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
  }

  /**
543
   * Returns the slider's model, which stores the minimum, maximum and current
544
   * values.
Tom Tromey committed
545 546
   *
   * @return The slider's model.
547
   *
548
   * @see #setModel(BoundedRangeModel)
Tom Tromey committed
549 550 551 552 553 554 555
   */
  public BoundedRangeModel getModel()
  {
    return sliderModel;
  }

  /**
556 557 558
   * Sets the slider's model and sends a {@link PropertyChangeEvent} (with the
   * property name "model") to all registered listeners.   The change listener
   * that the slider registered with the original model is removed and added
559
   * to the new model (this ensures that {@link ChangeEvent} notifications
560 561
   * generated by the model are automatically forwarded to listeners that are
   * registered with the slider).
Tom Tromey committed
562 563
   *
   * @param model The model to use with the slider.
564
   *
565
   * @see #getModel()
Tom Tromey committed
566 567 568 569 570 571 572 573
   */
  public void setModel(BoundedRangeModel model)
  {
    // I didn't do the null pointer check on purpose.
    // If you try it with Sun's, it'll go ahead and set it to null
    // and bork the next time it tries to access the model.
    if (model != sliderModel)
      {
574 575 576 577 578
        BoundedRangeModel oldModel = sliderModel;
        sliderModel = model;
        oldModel.removeChangeListener(changeListener);
        sliderModel.addChangeListener(changeListener);
        firePropertyChange("model", oldModel, sliderModel);
Tom Tromey committed
579 580 581 582
      }
  }

  /**
583
   * Returns the minimum value of the slider (from the slider's model).
Tom Tromey committed
584 585
   *
   * @return The minimum value of the slider.
586
   *
587
   * @see #setMinimum(int)
Tom Tromey committed
588 589 590 591 592 593 594
   */
  public int getMinimum()
  {
    return sliderModel.getMinimum();
  }

  /**
595
   * Sets the minimum value of the slider and fires a
596 597 598 599
   * {@link PropertyChangeEvent} (with the property name "minimum") to all
   * registered listeners.  Note that:
   * <p>
   * <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 604
   *     {@link ChangeEvent}.</li>
   * </ul>
605
   *
Tom Tromey committed
606
   * @param minimum The minimum value of the slider.
607
   *
608
   * @see #getMinimum()
Tom Tromey committed
609 610 611
   */
  public void setMinimum(int minimum)
  {
Tom Tromey committed
612
    int old = sliderModel.getMinimum();
Tom Tromey committed
613
    sliderModel.setMinimum(minimum);
Tom Tromey committed
614 615
    if (minimum != old)
      firePropertyChange("minimum", old, minimum);
Tom Tromey committed
616 617 618
  }

  /**
619
   * Returns the slider's maximum value (obtained from the slider's model).
Tom Tromey committed
620 621
   *
   * @return The maximum value of the slider.
622
   *
623
   * @see #setMaximum(int)
Tom Tromey committed
624 625 626 627 628 629 630
   */
  public int getMaximum()
  {
    return sliderModel.getMaximum();
  }

  /**
631
   * Sets the maximum value of the slider and fires a
632 633 634 635
   * {@link PropertyChangeEvent} (with the property name "maximum") to all
   * registered listeners.  Note that:
   * <p>
   * <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 640
   *     {@link ChangeEvent}.</li>
   * </ul>
Tom Tromey committed
641 642
   *
   * @param maximum The maximum value of the slider.
643
   *
644
   * @see #getMaximum()
Tom Tromey committed
645 646 647
   */
  public void setMaximum(int maximum)
  {
Tom Tromey committed
648
    int old = sliderModel.getMaximum();
Tom Tromey committed
649
    sliderModel.setMaximum(maximum);
Tom Tromey committed
650 651
    if (maximum != old)
      firePropertyChange("maximum", old, maximum);
Tom Tromey committed
652 653 654
  }

  /**
655
   * Returns the <code>valueIsAdjusting</code> flag from the slider's model.
Tom Tromey committed
656
   *
657
   * @return The <code>valueIsAdjusting</code> flag from the slider's model.
658
   *
659
   * @see #setValueIsAdjusting(boolean)
Tom Tromey committed
660 661 662 663 664 665 666
   */
  public boolean getValueIsAdjusting()
  {
    return sliderModel.getValueIsAdjusting();
  }

  /**
667
   * Sets the <code>valueIsAdjusting</code> flag in the slider's model, and
668
   * sends a {@link ChangeEvent} to all registered listeners.
Tom Tromey committed
669
   *
670
   * @param adjusting  the new flag value.
671
   *
672
   * @see #getValueIsAdjusting()
Tom Tromey committed
673 674 675 676 677 678 679
   */
  public void setValueIsAdjusting(boolean adjusting)
  {
    sliderModel.setValueIsAdjusting(adjusting);
  }

  /**
680
   * Returns the slider's extent value, obtained from the slider's model.
Tom Tromey committed
681
   *
682
   * @return The extent value.
683
   *
684
   * @see #setExtent(int)
Tom Tromey committed
685 686 687 688 689 690 691
   */
  public int getExtent()
  {
    return sliderModel.getExtent();
  }

  /**
692
   * Sets the slider's extent value and sends a {@link ChangeEvent} to all
693 694 695 696
   * registered listeners.  Note that the model will fire a change event to all
   * of its registered listeners first (with the model as the event source) and
   * then the slider will fire another change event to all of its registered
   * listeners (this time with the slider as the event source).
Tom Tromey committed
697 698
   *
   * @param extent The extent value for this slider.
699
   *
700
   * @see #getExtent()
Tom Tromey committed
701 702 703 704 705 706 707
   */
  public void setExtent(int extent)
  {
    sliderModel.setExtent(extent);
  }

  /**
708 709
   * Returns the orientation of the slider, either {@link JSlider#HORIZONTAL}
   * or {@link JSlider#VERTICAL}.
Tom Tromey committed
710 711
   *
   * @return The orientation of the slider.
712
   *
713
   * @see #setOrientation(int)
Tom Tromey committed
714 715 716 717 718 719 720
   */
  public int getOrientation()
  {
    return orientation;
  }

  /**
721
   * Sets the orientation for the slider and sends a
722 723
   * {@link PropertyChangeEvent} (with the property name "orientation") to all
   * registered listeners.
Tom Tromey committed
724
   *
725 726
   * @param orientation  the orientation (one of {@link JSlider#HORIZONTAL} or
   *     {@link JSlider#VERTICAL}).
727
   *
728 729
   * @throws IllegalArgumentException if <code>orientation</code> is not one of
   *     the permitted values.
730
   *
731
   * @see #getOrientation()
Tom Tromey committed
732 733 734 735
   */
  public void setOrientation(int orientation)
  {
    if (orientation != VERTICAL && orientation != HORIZONTAL)
736 737
      throw new IllegalArgumentException(
          "orientation must be one of: VERTICAL, HORIZONTAL");
Tom Tromey committed
738 739
    if (orientation != this.orientation)
      {
740 741 742
        int oldOrientation = this.orientation;
        this.orientation = orientation;
        firePropertyChange("orientation", oldOrientation, this.orientation);
743
        revalidate();
Tom Tromey committed
744 745 746 747
      }
  }

  /**
748
   * Returns the label table for the slider.
Tom Tromey committed
749
   *
750
   * @return The label table for the slider (possibly <code>null</code>).
751
   *
752
   * @see #setLabelTable(Dictionary)
Tom Tromey committed
753 754 755 756 757 758 759
   */
  public Dictionary getLabelTable()
  {
    return labelTable;
  }

  /**
760 761
   * Sets the table of labels for the slider and sends a
   * {@link PropertyChangeEvent} (with the property name "labelTable") to all
762
   * registered listeners.
Tom Tromey committed
763
   *
764
   * @param table  the table of labels (<code>null</code> permitted).
765
   *
766
   * @see #getLabelTable()
Tom Tromey committed
767 768 769 770 771
   */
  public void setLabelTable(Dictionary table)
  {
    if (table != labelTable)
      {
772 773
        Dictionary oldTable = labelTable;
        labelTable = table;
774
        updateLabelUIs();
775
        firePropertyChange("labelTable", oldTable, labelTable);
776 777
        revalidate();
        repaint();
Tom Tromey committed
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.
Tom Tromey committed
784 785 786
   */
  protected void updateLabelUIs()
  {
787
    if (labelTable != null)
Tom Tromey committed
788
      {
789 790 791 792 793 794 795 796 797 798
        for (Enumeration list = labelTable.elements(); list.hasMoreElements();)
          {
            Object o = list.nextElement();
            if (o instanceof JComponent)
              {
                JComponent jc = (JComponent) o;
                jc.updateUI();
                jc.setSize(jc.getPreferredSize());
              }
          }
Tom Tromey committed
799 800 801 802
      }
  }

  /**
803 804
   * Creates a hashtable of <code>(Integer, JLabel)</code> pairs that can be
   * used as a label table for this slider. The labels will start from the
805 806
   * slider's minimum and increase by the increment. Each label will have a text
   * string indicating its integer value.
Tom Tromey committed
807
   *
Tom Tromey committed
808
   * @param increment The increment between labels (must be > 0).
Tom Tromey committed
809
   *
810
   * @return A hashtable containing the labels.
Tom Tromey committed
811 812 813
   *
   * @throws IllegalArgumentException if <code>increment</code> is not greater
   *         than zero.
Tom Tromey committed
814 815 816 817 818 819 820
   */
  public Hashtable createStandardLabels(int increment)
  {
    return createStandardLabels(increment, sliderModel.getMinimum());
  }

  /**
821 822 823
   * Creates a hashtable of <code>(Integer, JLabel)</code> pairs that can be
   * used as a label table for this slider. The labels will start from the
   * given start value and increase by the increment. Each  label will have a
824
   * text string indicating its integer value.
Tom Tromey committed
825
   *
Tom Tromey committed
826
   * @param increment The increment between labels (must be > 0).
Tom Tromey committed
827 828 829
   * @param start The value to start from.
   *
   * @return A hashtable with the labels and their keys.
Tom Tromey committed
830 831 832 833
   *
   * @throws IllegalArgumentException if <code>increment</code> is not greater
   *         than zero, or <code>start</code> is not within the range of the
   *         model.
Tom Tromey committed
834 835 836
   */
  public Hashtable createStandardLabels(int increment, int start)
  {
837
    if (increment <= 0)
Tom Tromey committed
838 839 840
      throw new IllegalArgumentException("Requires 'increment' > 0.");
    if (start < getMinimum() || start > getMaximum())
      throw new IllegalArgumentException("The 'start' value is out of range.");
Tom Tromey committed
841
    Hashtable table = new Hashtable();
842
    int max = getMaximum();
Tom Tromey committed
843 844
    for (int i = start; i <= max; i += increment)
      {
845 846
        LabelUIResource label = new LabelUIResource(String.valueOf(i),
                                                    JLabel.CENTER);
847
        table.put(new Integer(i), label);
Tom Tromey committed
848 849 850 851 852
      }
    return table;
  }

  /**
853 854
   * Returns the flag that controls whether or not the value scale for the
   * slider is inverted (the default value is <code>false</code>).
Tom Tromey committed
855
   *
856 857
   * @return The flag that controls whether or not the value scale for the
   *     slider is inverted.
858
   *
859
   * @see #setInverted(boolean)
Tom Tromey committed
860 861 862 863 864 865 866
   */
  public boolean getInverted()
  {
    return isInverted;
  }

  /**
867 868 869
   * Sets the flag that controls whether or not the value scale for the
   * slider is inverted and, if the new flag value is different to the old flag
   * value, sends a {@link PropertyChangeEvent} to all registered listeners.
870 871
   * Typically, a horizontal slider will display a scale that increases from
   * left to right, but this is reversed if the 'inverted' flag is set to
872 873 874
   * <code>true</code>.  Similarly, a vertical slider will display a scale that
   * increases from bottom to top, and this is reversed if the 'inverted' flag
   * is set to <code>true</code>.
Tom Tromey committed
875
   *
876
   * @param inverted  the new flag value.
877
   *
878
   * @see #getInverted()
Tom Tromey committed
879 880 881 882 883
   */
  public void setInverted(boolean inverted)
  {
    if (isInverted != inverted)
      {
884 885 886
        boolean oldInverted = isInverted;
        isInverted = inverted;
        firePropertyChange("inverted", oldInverted, isInverted);
887
        repaint();
Tom Tromey committed
888 889 890 891
      }
  }

  /**
892
   * Returns the distance between major tick marks along the slider's value
893
   * scale.
Tom Tromey committed
894 895
   *
   * @return The amount of units between each major tick mark.
896
   *
897
   * @see #setMajorTickSpacing(int)
Tom Tromey committed
898 899 900 901 902 903 904
   */
  public int getMajorTickSpacing()
  {
    return majorTickSpacing;
  }

  /**
905 906
   * Sets the distance between major tick marks along the slider's value scale,
   * and sends a {@link PropertyChangeEvent} (with the property name
907
   * "majorTickSpacing") to all registered listeners.
Tom Tromey committed
908
   *
909
   * @param spacing  the distance between major tick marks.
910
   *
911
   * @see #getMajorTickSpacing()
Tom Tromey committed
912 913 914 915 916
   */
  public void setMajorTickSpacing(int spacing)
  {
    if (majorTickSpacing != spacing)
      {
917 918
        int oldSpacing = majorTickSpacing;
        majorTickSpacing = spacing;
919 920
        if (labelTable == null && majorTickSpacing > 0 && getPaintLabels())
          setLabelTable(createStandardLabels(majorTickSpacing));
921
        firePropertyChange("majorTickSpacing", oldSpacing, majorTickSpacing);
922 923
        if (getPaintTicks())
          repaint();
Tom Tromey committed
924 925 926 927
      }
  }

  /**
928
   * Returns the distance between minor tick marks along the slider's value
929
   * scale.
Tom Tromey committed
930
   *
931
   * @return The distance between minor tick marks along the slider's value
932
   *     scale.
933
   *
934
   * @see #setMinorTickSpacing(int)
Tom Tromey committed
935 936 937 938 939 940 941
   */
  public int getMinorTickSpacing()
  {
    return minorTickSpacing;
  }

  /**
942 943
   * Sets the distance between minor tick marks along the slider's value scale,
   * and sends a {@link PropertyChangeEvent} (with the property name
944
   * "minorTickSpacing") to all registered listeners.
Tom Tromey committed
945
   *
946
   * @param spacing  the distance between minor tick marks.
947
   *
948
   * @see #getMinorTickSpacing()
Tom Tromey committed
949 950 951 952 953
   */
  public void setMinorTickSpacing(int spacing)
  {
    if (minorTickSpacing != spacing)
      {
954 955 956
        int oldSpacing = minorTickSpacing;
        minorTickSpacing = spacing;
        firePropertyChange("minorTickSpacing", oldSpacing, minorTickSpacing);
957 958
        if (getPaintTicks())
          repaint();
Tom Tromey committed
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.
Tom Tromey committed
966
   *
967
   * @return <code>true</code> if the slider thumb automatically.
968
   *
969
   * @see #setSnapToTicks(boolean)
Tom Tromey committed
970 971 972 973 974 975 976
   */
  public boolean getSnapToTicks()
  {
    return snapToTicks;
  }

  /**
977 978 979
   * Sets the flag that controls whether the slider thumb will snap to ticks
   * and sends a {@link PropertyChangeEvent} (with the property name
   * 'snapToTicks') to all registered listeners. Sliders that snap to ticks
980
   * will automatically move the thumb to the nearest tick mark.
Tom Tromey committed
981
   *
982
   * @param snap  the new flag value.
983
   *
984
   * @see #getSnapToTicks()
Tom Tromey committed
985 986 987 988 989
   */
  public void setSnapToTicks(boolean snap)
  {
    if (snap != snapToTicks)
      {
990 991
        snapToTicks = snap;
        firePropertyChange("snapToTicks", !snap, snap);
Tom Tromey committed
992 993 994 995
      }
  }

  /**
996 997
   * Returns the flag that controls whether or not tick marks are painted along
   * the slider's value scale.
Tom Tromey committed
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)
Tom Tromey committed
1003 1004 1005 1006 1007 1008 1009
   */
  public boolean getPaintTicks()
  {
    return paintTicks;
  }

  /**
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 1013
   * the property name "paintTicks") to all registered listeners. In
   * 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.
Tom Tromey committed
1016 1017
   *
   * @param paint Whether ticks will be painted.
1018
   *
1019
   * @see #getPaintTicks()
Tom Tromey committed
1020 1021 1022 1023 1024
   */
  public void setPaintTicks(boolean paint)
  {
    if (paint != paintTicks)
      {
1025 1026 1027
        boolean oldPaintTicks = paintTicks;
        paintTicks = paint;
        firePropertyChange("paintTicks", oldPaintTicks, paintTicks);
1028 1029
        revalidate();
        repaint();
Tom Tromey committed
1030 1031 1032 1033
      }
  }

  /**
1034
   * Returns the flag that controls whether or not the track is painted.
Tom Tromey committed
1035 1036
   *
   * @return Whether the track will be painted.
1037
   *
1038
   * @see #setPaintTrack(boolean)
Tom Tromey committed
1039 1040 1041 1042 1043 1044 1045
   */
  public boolean getPaintTrack()
  {
    return paintTrack;
  }

  /**
Tom Tromey committed
1046 1047 1048
   * Sets the flag that controls whether or not the track is painted, and
   * sends a {@link PropertyChangeEvent} (for the "paintTrack" property) to all
   * registered listeners.
Tom Tromey committed
1049 1050
   *
   * @param paint Whether the track will be painted.
1051
   *
1052
   * @see #getPaintTrack()
Tom Tromey committed
1053 1054 1055
   */
  public void setPaintTrack(boolean paint)
  {
Tom Tromey committed
1056 1057 1058 1059
    if (paintTrack != paint)
    {
      paintTrack = paint;
      firePropertyChange("paintTrack", !paint, paint);
1060
      repaint();
Tom Tromey committed
1061
    }
Tom Tromey committed
1062 1063 1064
  }

  /**
1065 1066
   * Returns the flag that controls whether or not labels are painted for the
   * tick marks along the slider.
Tom Tromey committed
1067 1068
   *
   * @return Whether labels will be painted.
1069
   *
1070
   * @see #setPaintLabels(boolean)
Tom Tromey committed
1071 1072 1073 1074 1075 1076 1077
   */
  public boolean getPaintLabels()
  {
    return paintLabels;
  }

  /**
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.
Tom Tromey committed
1081 1082
   *
   * @param paint Whether labels will be painted.
1083
   *
1084
   * @see #getPaintLabels()
Tom Tromey committed
1085 1086 1087 1088 1089
   */
  public void setPaintLabels(boolean paint)
  {
    if (paint != paintLabels)
      {
1090 1091
        paintLabels = paint;
        if (paint && majorTickSpacing > 0 && labelTable == null)
1092
          setLabelTable(createStandardLabels(majorTickSpacing));
1093
        firePropertyChange("paintLabels", !paint, paint);
1094 1095
        revalidate();
        repaint();
Tom Tromey committed
1096 1097 1098 1099
      }
  }

  /**
1100 1101
   * Returns an implementation-dependent string describing the attributes of
   * this <code>JSlider</code>.
Tom Tromey committed
1102
   *
1103 1104
   * @return A string describing the attributes of this <code>JSlider</code>
   *         (never <code>null</code>).
Tom Tromey committed
1105 1106 1107
   */
  protected String paramString()
  {
1108
    String superParamStr = super.paramString();
1109
    CPStringBuilder sb = new CPStringBuilder();
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
    sb.append(",isInverted=").append(getInverted());
    sb.append(",majorTickSpacing=").append(getMajorTickSpacing());
    sb.append(",minorTickSpacing=").append(getMinorTickSpacing());
    sb.append(",orientation=");
    if (orientation == HORIZONTAL)
      sb.append("HORIZONTAL");
    else
      sb.append("VERTICAL");
    sb.append(",paintLabels=").append(getPaintLabels());
    sb.append(",paintTicks=").append(getPaintTicks());
    sb.append(",paintTrack=").append(getPaintTrack());
    sb.append(",snapToTicks=").append(getSnapToTicks());
1122

1123 1124 1125 1126 1127 1128
    // the following is output by the reference implementation.  We don't
    // strictly need to replicate this. Perhaps it has some meaning, but
    // I couldn't determine it yet...
    sb.append(",snapToValue=true");

    return superParamStr + sb.toString();
Tom Tromey committed
1129 1130 1131
  }

  /**
1132 1133
   * Returns the object that provides accessibility features for this
   * <code>JSlider</code> component.
Tom Tromey committed
1134
   *
1135
   * @return The accessible context (an instance of {@link AccessibleJSlider}).
Tom Tromey committed
1136 1137 1138 1139 1140
   */
  public AccessibleContext getAccessibleContext()
  {
    if (accessibleContext == null)
      accessibleContext = new AccessibleJSlider();
1141

Tom Tromey committed
1142 1143 1144
    return accessibleContext;
  }
}