1 /*
2 * @(#)MultiThumbSlider.java
3 *
4 * $Date: 2015-01-04 21:15:07 -0500 (Sun, 04 Jan 2015) $
5 *
6 * Copyright (c) 2011 by Jeremy Wood.
7 * All rights reserved.
8 *
9 * The copyright of this software is owned by Jeremy Wood.
10 * You may not use, copy or modify this software, except in
11 * accordance with the license agreement you entered into with
12 * Jeremy Wood. For details see accompanying license terms.
13 *
14 * This software is probably, but not necessarily, discussed here:
15 * https://javagraphics.java.net/
16 *
17 * That site should also contain the most recent official version
18 * of this software. (See the SVN repository for more details.)
19 */
20 package com.bric.multislider;
21
22 import java.lang.reflect.Array;
23 import java.lang.reflect.Constructor;
24 import java.util.List;
25 import java.util.Vector;
26
27 import javax.swing.JComponent;
28 import javax.swing.SwingConstants;
29 import javax.swing.UIManager;
30 import javax.swing.event.ChangeEvent;
31 import javax.swing.event.ChangeListener;
32 import javax.swing.plaf.ComponentUI;
33
34 /**
35 * This JComponent resembles a <code>JSlider</code>, except there are at least two thumbs. A <code>JSlider</code> is designed to
36 * modify one number within a certain range of values. By contrast a <code>MultiThumbSlider</code> actually modifies a
37 * <i>table</i> of data. Each thumb in a <code>MultiThumbSlider</code> should be thought of as a key, and it maps to an abstract
38 * value. In the case of the <code>GradientSlider</code>: each value is a <code>java.awt.Color</code>. Other subclasses could
39 * come along that map to other abstract objects. (For example, a <code>VolumeSlider</code> might map each thumb to a specific
40 * volume level. This type of widget would let the user control fading in/out of an audio track.)
41 * <P>
42 * The slider graphically represents the domain from zero to one, so each thumb is always positioned within that domain. If the
43 * user drags a thumb outside this domain: that thumb disappears.
44 * <P>
45 * There is always a selected thumb in each slider when this slider has the keyboard focus. The user can press the tab key (or
46 * shift-tab) to transfer focus to different thumbs. Also the arrow keys can be used to control the selected thumb.
47 * <P>
48 * The user can click and drag any thumb to a new location. If a thumb is dragged so it is less than zero or greater than one:
49 * then that thumb is removed. If the user clicks between two existing thumbs: a new thumb is created if <code>autoAdd</code> is
50 * set to <code>true</code>. (If <code>autoAdd</code> is set to false: nothing happens.)
51 * <P>
52 * There are unimplemented methods in this class: <code>doDoubleClick()</code> and <code>doPopup()</code>. The UI will invoke
53 * these methods as needed; this gives the user a chance to edit the values represented at a particular point.
54 * <P>
55 * Also using the keyboard:
56 * <ul>
57 * <LI>In a horizontal slider, the user can press modifier+left or modifer+right to insert a new thumb to the left/right of the
58 * currently selected thumb. (Where "modifier" refers to <code>Toolkit.getDefaultTookit().getMenuShortcutKeyMask()</code>. On
59 * Mac this is META, and on Windows this is CONTROL.) Likewise on a vertical slider the up/down arrow keys can be used to add
60 * thumbs.</li>
61 * <LI>The delete/backspace key can be used to remove thumbs.</li>
62 * <LI>In a horizontal slider, the down arrow key can be used to invoke <code>doPopup()</code>. This should invoke a
63 * <code>JPopupMenu</code> that is keyboard accessible, so the user should be able to navigate this component without a mouse.
64 * Likewise on a vertical slider the right arrow key should do the same.</li>
65 * <LI>The space bar or return key invokes <code>doDoubleClick()</code>.</LI>
66 * </ul>
67 * <P>
68 * Because thumbs can be abstractly inserted, the values each thumb represents should be tween-able. That is, if there is a
69 * value at zero and a value at one, the call <code>getValue(.5f)</code> must return a value that is halfway between those
70 * values.
71 * <P>
72 * Also note that although the thumbs must always be between zero and one: the minimum and maximum thumbs do not have to be zero
73 * and one. The user can adjust them so the minimum thumb is, say, .2f, and the maximum thumb is .5f.
74 * @param <T> the type of data each float maps to. For example: in the GradientSlider this value is a Color. Sometimes this
75 * property may be unnecessary. If this slider is only meant to store the relative position of thumbs, then you may
76 * set this to a trivial stub-like object like a String or Character.
77 */
78 public class MultiThumbSlider<T> extends JComponent
79 {
80 @SuppressWarnings("javadoc")
81 private static final long serialVersionUID = 1L;
82
83 /** A set of possible behaviors when one thumb collides with another. */
84 public static enum Collision
85 {
86 /** When the user drags one thumb and it collides with another, nudge the other thumb as far as possible. */
87 NUDGE_OTHER,
88 /** When the user drags one thumb and it collides with another, skip over the other thumb. */
89 JUMP_OVER_OTHER,
90 /**
91 * When the user drags one thumb and it collides with another, bump into the other thumb and don't allow any more
92 * movement.
93 */
94 STOP_AGAINST
95 };
96
97 /** The property that controls whether clicking between thumbs automatically adds a thumb. */
98 public static final String AUTOADD_PROPERTY = MultiThumbSlider.class.getName() + ".auto-add";
99
100 /** The property that controls whether the user can remove a thumb (either by dragging or with the delete key). */
101 public static final String REMOVAL_ALLOWED = MultiThumbSlider.class.getName() + ".removal-allowed";
102
103 /** The property that is changed when <code>setSelectedThumb()</code> is called. */
104 public static final String SELECTED_THUMB_PROPERTY = MultiThumbSlider.class.getName() + ".selected-thumb";
105
106 /** The property that is changed when <code>setCollisionPolicy(c)</code> is called. */
107 public static final String COLLISION_PROPERTY = MultiThumbSlider.class.getName() + ".collision";
108
109 /** The property that is changed when <code>setInverted(b)</code> is called. */
110 public static final String INVERTED_PROPERTY = MultiThumbSlider.class.getName() + ".inverted";
111
112 /** The property that is changed when <code>setInverted(b)</code> is called. */
113 public static final String THUMB_OVERLAP_PROPERTY = MultiThumbSlider.class.getName() + ".thumb-overlap";
114
115 /** The property that is changed when <code>setMinimumThumbnailCount(b)</code> is called. */
116 public static final String THUMB_MINIMUM_PROPERTY = MultiThumbSlider.class.getName() + ".thumb-minimum";
117
118 /** The property that is changed when <code>setOrientation(i)</code> is called. */
119 public static final String ORIENTATION_PROPERTY = MultiThumbSlider.class.getName() + ".orientation";
120
121 /**
122 * The property that is changed when <code>setValues()</code> is called. Note this is used when either the positions or the
123 * values are updated, because they need to be updated at the same time to maintain an exact one-to-one ratio.
124 */
125 public static final String VALUES_PROPERTY = MultiThumbSlider.class.getName() + ".values";
126
127 /** The property that is changed when <code>setValueIsAdjusting(b)</code> is called. */
128 public static final String ADJUST_PROPERTY = MultiThumbSlider.class.getName() + ".adjusting";
129
130 /** The property that is changed when <code>setPaintTicks(b)</code> is called. */
131 public static final String PAINT_TICKS_PROPERTY = MultiThumbSlider.class.getName() + ".paint ticks";
132
133 /** The positions of the thumbs */
134 protected float[] thumbPositions = new float[0];
135
136 /** The values for each thumb */
137 protected T[] values;
138
139 /** ChangeListeners registered with this slider. */
140 List<ChangeListener> changeListeners;
141
142 /**
143 * The orientation constant for a horizontal slider.
144 */
145 public static final int HORIZONTAL = SwingConstants.HORIZONTAL;
146
147 /**
148 * The orientation constant for a vertical slider.
149 */
150 public static final int VERTICAL = SwingConstants.VERTICAL;
151
152 /**
153 * Creates a new horizontal MultiThumbSlider.
154 * @param thumbPositions an array of values from zero to one.
155 * @param values an array of values, each value corresponds to a value in <code>thumbPositions</code>.
156 */
157 public MultiThumbSlider(float[] thumbPositions, T[] values)
158 {
159 this(HORIZONTAL, thumbPositions, values);
160 }
161
162 /**
163 * Creates a new MultiThumbSlider.
164 * @param orientation must be <code>HORIZONTAL</code> or <code>VERTICAL</code>
165 * @param thumbPositions an array of values from zero to one.
166 * @param values an array of values, each value corresponds to a value in <code>thumbPositions</code>.
167 */
168 public MultiThumbSlider(int orientation, float[] thumbPositions, T[] values)
169 {
170 setOrientation(orientation);
171 setValues(thumbPositions, values);
172 setFocusable(true);
173 updateUI();
174 }
175
176 @SuppressWarnings({"unchecked", "javadoc"})
177 public MultiThumbSliderUi<T> getUI()
178 {
179 return (MultiThumbSliderUi<T>) this.ui;
180 }
181
182 @Override
183 public void updateUI()
184 {
185 String name = UIManager.getString("MultiThumbSliderUI");
186 if (name == null)
187 {
188 if (Jvm.isMac)
189 {
190 name = "com.bric.multislider.AquaMultiThumbSliderUI";
191 }
192 else if (Jvm.isWindows)
193 {
194 name = "com.bric.multislider.VistaMultiThumbSliderUI";
195 }
196 else
197 {
198 name = "com.bric.multislider.DefaultMultiThumbSliderUI";
199 }
200 }
201 try
202 {
203 Class<?> c = Class.forName(name);
204 Constructor<?>[] constructors = c.getConstructors();
205 for (int a = 0; a < constructors.length; a++)
206 {
207 Class<?>[] types = constructors[a].getParameterTypes();
208 if (types.length == 1 && types[0].equals(MultiThumbSlider.class))
209 {
210 MultiThumbSliderUi<T> ui = (MultiThumbSliderUi<T>) constructors[a].newInstance(new Object[] {this});
211 setUI(ui);
212 return;
213 }
214 }
215 }
216 catch (ClassNotFoundException e)
217 {
218 throw new RuntimeException("The class \"" + name + "\" could not be found.");
219 }
220 catch (Throwable t)
221 {
222 RuntimeException e = new RuntimeException("The class \"" + name + "\" could not be constructed.");
223 e.initCause(t);
224 throw e;
225 }
226 }
227
228 /**
229 * @param ui slider
230 */
231 public void setUI(MultiThumbSliderUi<T> ui)
232 {
233 super.setUI((ComponentUI) ui);
234 }
235
236 /**
237 * This listener will be notified when the colors/positions of this slider are modified.
238 * <P>
239 * Note you can also listen to these events by listening to the <code>VALUES_PROPERTY</code>, but this mechanism is provided
240 * as a convenience to resemble the <code>JSlider</code> model.
241 * @param l the <code>ChangeListener</code> to add.
242 */
243 public void addChangeListener(ChangeListener l)
244 {
245 if (this.changeListeners == null)
246 this.changeListeners = new Vector<ChangeListener>();
247 if (this.changeListeners.contains(l))
248 return;
249 this.changeListeners.add(l);
250 }
251
252 /**
253 * Removes a <code>ChangeListener</code> from this slider.
254 * @param l the listener to remove
255 */
256 public void removeChangeListener(ChangeListener l)
257 {
258 if (this.changeListeners == null)
259 return;
260 this.changeListeners.remove(l);
261 }
262
263 /** Invokes all the ChangeListeners. */
264 protected void fireChangeListeners()
265 {
266 if (this.changeListeners == null)
267 return;
268 for (int a = 0; a < this.changeListeners.size(); a++)
269 {
270 try
271 {
272 (this.changeListeners.get(a)).stateChanged(new ChangeEvent(this));
273 }
274 catch (Throwable t)
275 {
276 t.printStackTrace();
277 }
278 }
279 }
280
281 /**
282 * Depending on which thumb is selected, this may shift the focus to the next available thumb, or it may shift the focus to
283 * the next focusable <code>JComponent</code>.
284 */
285 @Override
286 public void transferFocus()
287 {
288 transferFocus(true);
289 }
290
291 /**
292 * Shifts the focus forward or backward. This may decide to select another thumb, or it may call
293 * <code>super.transferFocus()</code> to let the next JComponent receive the focus.
294 * @param forward whether we're shifting forward or backward
295 */
296 private void transferFocus(boolean forward)
297 {
298 int direction = (forward) ? 1 : -1;
299
300 // because vertical sliders are technically inverted already:
301 if (getOrientation() == VERTICAL)
302 direction = direction * -1;
303
304 // because inverted sliders are, well, inverted:
305 if (isInverted())
306 direction = direction * -1;
307
308 int selectedThumb = getSelectedThumb();
309 if (direction == 1)
310 {
311 if (selectedThumb != this.thumbPositions.length - 1)
312 {
313 setSelectedThumb(selectedThumb + 1);
314 return;
315 }
316 }
317 else
318 {
319 if (selectedThumb != 0)
320 {
321 setSelectedThumb(selectedThumb - 1);
322 return;
323 }
324 }
325 if (forward)
326 {
327 super.transferFocus();
328 }
329 else
330 {
331 super.transferFocusBackward();
332 }
333 }
334
335 /**
336 * Depending on which thumb is selected, this may shift the focus to the previous available thumb, or it may shift the focus
337 * to the previous focusable <code>JComponent</code>.
338 */
339 @Override
340 public void transferFocusBackward()
341 {
342 transferFocus(false);
343 }
344
345 /**
346 * This creates a new value for insertion.
347 * <P>
348 * If the <code>pos</code> argument is outside the domain of thumbs, then a value still needs to be returned.
349 * @param pos a position between zero and one
350 * @return a value that corresponds to the position <code>pos</code>
351 */
352 public T createValueForInsertion(float pos)
353 {
354 throw new NullPointerException(
355 "this method is undefined. Either auto-adding should be disabled, or this method needs to be overridden to return a value");
356 }
357
358 /**
359 * Removes a specific thumb
360 * @param thumbIndex the thumb index to remove.
361 */
362 public void removeThumb(int thumbIndex)
363 {
364 if (thumbIndex < 0 || thumbIndex > this.thumbPositions.length)
365 throw new IllegalArgumentException("There is no thumb at index " + thumbIndex + " to remove.");
366
367 float[] f = new float[this.thumbPositions.length - 1];
368 T[] c = createSimilarArray(this.values, this.values.length - 1);
369 System.arraycopy(this.thumbPositions, 0, f, 0, thumbIndex);
370 System.arraycopy(this.values, 0, c, 0, thumbIndex);
371 System.arraycopy(this.thumbPositions, thumbIndex + 1, f, thumbIndex, f.length - thumbIndex);
372 System.arraycopy(this.values, thumbIndex + 1, c, thumbIndex, f.length - thumbIndex);
373 setValues(f, c);
374 }
375
376 /**
377 * This is a kludgy casting trick to make our arrays mesh with generics.
378 * @param srcArray source array
379 * @param length the length
380 * @return array of type T
381 */
382 private T[] createSimilarArray(T[] srcArray, int length)
383 {
384 Class<?> componentType = srcArray.getClass().getComponentType();
385 return (T[]) Array.newInstance(componentType, length);
386 }
387
388 /**
389 * An optional method subclasses can override to react to the user's double-click. When a thumb is double-clicked the user
390 * is trying to edit the value for that thumb. A double-click probably suggests the user wants a detailed set of controls to
391 * edit a value, such as a dialog.
392 * <P>
393 * Note this method will be called with arguments (-1,-1) if the space bar or return key is pressed.
394 * <P>
395 * By default this method does nothing, and returns <code>false</code>
396 * <P>
397 * Note the (x,y) information passed to this method is only provided so subclasses can position components (such as a
398 * JPopupMenu). It can be assumed for a double-click event that the user has selected a thumb (since one click will
399 * click/create a thumb) and intends to edit the currently selected thumb.
400 * @param x the x-value of the mouse click location
401 * @param y the y-value of the mouse click location
402 * @return <code>true</code> if this event was consumed, or acted upon. <code>false</code> if this is unimplemented.
403 */
404 public boolean doDoubleClick(int x, int y)
405 {
406 return false;
407 }
408
409 /**
410 * An optional method subclasses can override to react to the user's request for a contextual menu. When a thumb is
411 * right-clicked the user is trying to edit the value for that thumb. A right-click probably suggests the user wants very
412 * quick, simple options to adjust a thumb.
413 * <P>
414 * By default this method does nothing, and returns <code>false</code>
415 * @param x the x-value of the mouse click location
416 * @param y the y-value of the mouse click location
417 * @return <code>true</code> if this event was consumed, or acted upon. <code>false</code> if this is unimplemented.
418 */
419 public boolean doPopup(int x, int y)
420 {
421 return false;
422 }
423
424 /**
425 * Tells if tick marks are to be painted.
426 * @return whether ticks should be painted on this slider.
427 */
428 public boolean isPaintTicks()
429 {
430 Boolean b = (Boolean) getClientProperty(PAINT_TICKS_PROPERTY);
431 if (b == null)
432 return false;
433 return b;
434 }
435
436 /**
437 * Turns on/off the painted tick marks for this slider.
438 * <P>
439 * This triggers a <code>PropertyChangeEvent</code> for <code>PAINT_TICKS_PROPERTY</code>.
440 * @param b whether tick marks should be painted
441 */
442 public void setPaintTicks(boolean b)
443 {
444 putClientProperty(PAINT_TICKS_PROPERTY, b);
445 }
446
447 /**
448 * This creats and inserts a thumb at a position indicated.
449 * <P>
450 * This method relies on the abstract <code>getValue(float)</code> to determine what value to put at the new thumb location.
451 * @param pos the new thumb position
452 * @return the index of the newly created thumb
453 */
454 public int addThumb(float pos)
455 {
456 if (pos < 0 || pos > 1)
457 throw new IllegalArgumentException("the new position (" + pos + ") must be between zero and one");
458 T newValue = createValueForInsertion(pos);
459 float[] f = new float[this.thumbPositions.length + 1];
460 T[] c = createSimilarArray(this.values, this.values.length + 1);
461
462 int newIndex = -1;
463 if (pos < this.thumbPositions[0])
464 {
465 System.arraycopy(this.thumbPositions, 0, f, 1, this.thumbPositions.length);
466 System.arraycopy(this.values, 0, c, 1, this.values.length);
467 newIndex = 0;
468 f[0] = pos;
469 c[0] = newValue;
470 }
471 else if (pos > this.thumbPositions[this.thumbPositions.length - 1])
472 {
473 System.arraycopy(this.thumbPositions, 0, f, 0, this.thumbPositions.length);
474 System.arraycopy(this.values, 0, c, 0, this.values.length);
475 newIndex = f.length - 1;
476 f[f.length - 1] = pos;
477 c[c.length - 1] = newValue;
478 }
479 else
480 {
481 boolean addedYet = false;
482 for (int a = 0; a < f.length; a++)
483 {
484 if (addedYet == false && this.thumbPositions[a] < pos)
485 {
486 f[a] = this.thumbPositions[a];
487 c[a] = this.values[a];
488 }
489 else
490 {
491 if (addedYet == false)
492 {
493 c[a] = newValue;
494 f[a] = pos;
495 addedYet = true;
496 newIndex = a;
497 }
498 else
499 {
500 f[a] = this.thumbPositions[a - 1];
501 c[a] = this.values[a - 1];
502 }
503 }
504 }
505 }
506 setValues(f, c);
507 return newIndex;
508 }
509
510 /**
511 * This is used to notify other objects when the user is in the process of adjusting values in this slider.
512 * <P>
513 * A listener may not want to act on certain changes until this property is <code>false</code> if it is expensive to process
514 * certain changes.
515 * <P>
516 * This triggers a <code>PropertyChangeEvent</code> for <code>ADJUST_PROPERTY</code>.
517 * @param b new value
518 */
519 public void setValueIsAdjusting(boolean b)
520 {
521 putClientProperty(ADJUST_PROPERTY, b);
522 }
523
524 /**
525 * <code>true</code> if the user is current modifying this component.
526 * @return the value of the <code>adjusting</code> property
527 */
528 public boolean isValueAdjusting()
529 {
530 Boolean b = (Boolean) getClientProperty(ADJUST_PROPERTY);
531 if (b == null)
532 return false;
533 return b;
534 }
535
536 /**
537 * The thumb positions for this slider.
538 * <P>
539 * There is a one-to-one correspondence between this array and the <code>getValues()</code> array.
540 * <P>
541 * This array is always sorted in ascending order.
542 * @return an array of the positions of thumbs.
543 */
544 public float[] getThumbPositions()
545 {
546 float[] f = new float[this.thumbPositions.length];
547 System.arraycopy(this.thumbPositions, 0, f, 0, f.length);
548 return f;
549 }
550
551 /**
552 * The values for thumbs for this slider.
553 * <P>
554 * There is a one-to-one correspondence between this array and the <code>getThumbPositions()</code> array.
555 * @return an array of the values associated with each thumb.
556 */
557 public T[] getValues()
558 {
559 T[] c = createSimilarArray(this.values, this.values.length);
560 System.arraycopy(this.values, 0, c, 0, c.length);
561 return c;
562 }
563
564 /**
565 * @param f an array of floats
566 * @return a string representation of f
567 */
568 private static String toString(float[] f)
569 {
570 StringBuffer sb = new StringBuffer();
571 sb.append('[');
572 for (int a = 0; a < f.length; a++)
573 {
574 sb.append(Float.toString(f[a]));
575 if (a != f.length - 1)
576 {
577 sb.append(", ");
578 }
579 }
580 sb.append(']');
581 return sb.toString();
582 }
583
584 /**
585 * This assigns new positions/values for the thumbs in this slider. The two must be assigned at exactly the same time, so
586 * there is always the same number of thumbs/sliders.
587 * <P>
588 * This triggers a <code>PropertyChangeEvent</code> for <code>VALUES_PROPERTY</code>, and possibly for the
589 * <code>SELECTED_THUMB_PROPERTY</code> if that had to be adjusted, too.
590 * @param thumbPositions an array of the new position of each thumb
591 * @param values an array of the value associated with each thumb
592 * @throws IllegalArgumentException if the size of the arrays are different, or if the thumbPositions array is not sorted in
593 * ascending order.
594 */
595 public void setValues(float[] thumbPositions, T[] values)
596 {
597 if (values.length != thumbPositions.length)
598 throw new IllegalArgumentException("there number of positions (" + thumbPositions.length
599 + ") must equal the number of values (" + values.length + ")");
600
601 for (int a = 0; a < values.length; a++)
602 {
603 if (values[a] == null)
604 throw new NullPointerException();
605 if (a > 0 && thumbPositions[a] < thumbPositions[a - 1])
606 throw new IllegalArgumentException(
607 "the thumb positions must be ascending order (" + toString(thumbPositions) + ")");
608 if (thumbPositions[a] < 0 || thumbPositions[a] > 1)
609 throw new IllegalArgumentException(
610 "illegal thumb value " + thumbPositions[a] + " (must be between zero and one)");
611 }
612
613 // don't clone arrays and fire off events if
614 // there really is no change here:
615 if (thumbPositions.length == this.thumbPositions.length)
616 {
617 boolean equal = true;
618 for (int a = 0; a < thumbPositions.length && equal; a++)
619 {
620 if (thumbPositions[a] != this.thumbPositions[a])
621 equal = false;
622 }
623 for (int a = 0; a < values.length && equal; a++)
624 {
625 if (!values[a].equals(this.values[a]))
626 equal = false;
627 }
628 if (equal)
629 return; // no change! go home.
630 }
631
632 this.thumbPositions = new float[thumbPositions.length];
633 System.arraycopy(thumbPositions, 0, this.thumbPositions, 0, thumbPositions.length);
634 this.values = createSimilarArray(values, values.length);
635 System.arraycopy(values, 0, this.values, 0, values.length);
636 int oldThumb = getSelectedThumb();
637 int newThumb = oldThumb;
638 if (newThumb >= thumbPositions.length)
639 {
640 newThumb = thumbPositions.length - 1;
641 }
642 firePropertyChange(VALUES_PROPERTY, null, values);
643 if (oldThumb != newThumb)
644 {
645 setSelectedThumb(newThumb);
646 }
647 fireChangeListeners();
648 }
649
650 /**
651 * The number of thumbs in this slider.
652 * @return the number of thumbs.
653 */
654 public int getThumbCount()
655 {
656 return this.thumbPositions.length;
657 }
658
659 /**
660 * Assigns the currently selected thumb. A value of -1 indicates that no thumb is currently selected.
661 * <P>
662 * A slider should always have a selected thumb if it has the keyboard focus, though, so be careful when you modify this.
663 * <P>
664 * This triggers a <code>PropertyChangeEvent</code> for <code>SELECTED_THUMB_PROPERTY</code>.
665 * @param index the new selected thumb
666 */
667 public void setSelectedThumb(int index)
668 {
669 putClientProperty(SELECTED_THUMB_PROPERTY, new Integer(index));
670 }
671
672 /**
673 * Returns the selected thumb index, or -1 if this component doesn't have the keyboard focus.
674 * @return the selected thumb index
675 */
676 public int getSelectedThumb()
677 {
678 return getSelectedThumb(true);
679 }
680
681 /**
682 * Returns the currently selected thumb index.
683 * <P>
684 * Note this might be -1, indicating that there is no selected thumb.
685 * <P>
686 * It is recommend you use the <code>getSelectedThumb()</code> method most of the time. This method is made public so UI's
687 * can provide a better user experience as this component gains and loses focus.
688 * @param ignoreIfUnfocused if this component doesn't have focus and this is <code>true</code>, then this returns -1. If
689 * this is <code>false</code> then this returns the internal value used to store the selected index, but the user
690 * may not realize this thumb is "selected".
691 * @return the selected thumb
692 */
693 public int getSelectedThumb(boolean ignoreIfUnfocused)
694 {
695 if (hasFocus() == false && ignoreIfUnfocused)
696 return -1;
697 Integer i = (Integer) getClientProperty(SELECTED_THUMB_PROPERTY);
698 if (i == null)
699 return -1;
700 return i.intValue();
701 }
702
703 /**
704 * Controls whether thumbs are automatically added when the user clicks in a space that doesn't already have a thumb.
705 * @param b whether auto adding is active or not
706 */
707 public void setAutoAdding(boolean b)
708 {
709 putClientProperty(AUTOADD_PROPERTY, b);
710 }
711
712 /**
713 * @return whether thumbs are automatically added when the user clicks in a space that doesn't already have a thumb.
714 */
715 public boolean isAutoAdding()
716 {
717 Boolean b = (Boolean) getClientProperty(AUTOADD_PROPERTY);
718 if (b == null)
719 return true;
720 return b;
721 }
722
723 /**
724 * The orientation of this slider.
725 * @return HORIZONTAL or VERTICAL
726 */
727 public int getOrientation()
728 {
729 Integer i = (Integer) getClientProperty(ORIENTATION_PROPERTY);
730 if (i == null)
731 return HORIZONTAL;
732 return i;
733 }
734
735 /**
736 * Reassign the orientation of this slider.
737 * @param i must be HORIZONTAL or VERTICAL
738 */
739 public void setOrientation(int i)
740 {
741 if (!(i == SwingConstants.HORIZONTAL || i == SwingConstants.VERTICAL))
742 throw new IllegalArgumentException("the orientation must be HORIZONTAL or VERTICAL");
743 putClientProperty(ORIENTATION_PROPERTY, i);
744 }
745
746 /**
747 * @return whether this slider is inverted or not.
748 */
749 public boolean isInverted()
750 {
751 Boolean b = (Boolean) getClientProperty(INVERTED_PROPERTY);
752 if (b == null)
753 return false;
754 return b;
755 }
756
757 /**
758 * Assigns whether this slider is inverted or not.
759 * @param b inverted slider or not This triggers a <code>PropertyChangeEvent</code> for <code>INVERTED_PROPERTY</code>.
760 */
761 public void setInverted(boolean b)
762 {
763 putClientProperty(INVERTED_PROPERTY, b);
764 }
765
766 public Collision getCollisionPolicy()
767 {
768 Collision c = (Collision) getClientProperty(COLLISION_PROPERTY);
769 if (c == null)
770 c = Collision.JUMP_OVER_OTHER;
771 return c;
772 }
773
774 public void setCollisionPolicy(Collision c)
775 {
776 putClientProperty(COLLISION_PROPERTY, c);
777 }
778
779 public boolean isThumbRemovalAllowed()
780 {
781 Boolean b = (Boolean) getClientProperty(REMOVAL_ALLOWED);
782 if (b == null)
783 b = true;
784 return b;
785 }
786
787 public void setThumbRemovalAllowed(boolean b)
788 {
789 putClientProperty(REMOVAL_ALLOWED, b);
790 }
791
792 public void setMinimumThumbnailCount(int i)
793 {
794 putClientProperty(THUMB_MINIMUM_PROPERTY, i);
795 }
796
797 public int getMinimumThumbnailCount()
798 {
799 Integer i = (Integer) getClientProperty(THUMB_MINIMUM_PROPERTY);
800 if (i == null)
801 return 1;
802 return i;
803 }
804
805 public void setThumbOverlap(boolean i)
806 {
807 putClientProperty(THUMB_OVERLAP_PROPERTY, i);
808 }
809
810 public boolean isThumbOverlap()
811 {
812 Boolean b = (Boolean) getClientProperty(THUMB_OVERLAP_PROPERTY);
813 if (b == null)
814 return false;
815 return b;
816 }
817 }