1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public class MultiThumbSlider<T> extends JComponent
79 {
80 @SuppressWarnings("javadoc")
81 private static final long serialVersionUID = 1L;
82
83
84 public static enum Collision
85 {
86
87 NUDGE_OTHER,
88
89 JUMP_OVER_OTHER,
90
91
92
93
94 STOP_AGAINST
95 };
96
97
98 public static final String AUTOADD_PROPERTY = MultiThumbSlider.class.getName() + ".auto-add";
99
100
101 public static final String REMOVAL_ALLOWED = MultiThumbSlider.class.getName() + ".removal-allowed";
102
103
104 public static final String SELECTED_THUMB_PROPERTY = MultiThumbSlider.class.getName() + ".selected-thumb";
105
106
107 public static final String COLLISION_PROPERTY = MultiThumbSlider.class.getName() + ".collision";
108
109
110 public static final String INVERTED_PROPERTY = MultiThumbSlider.class.getName() + ".inverted";
111
112
113 public static final String THUMB_OVERLAP_PROPERTY = MultiThumbSlider.class.getName() + ".thumb-overlap";
114
115
116 public static final String THUMB_MINIMUM_PROPERTY = MultiThumbSlider.class.getName() + ".thumb-minimum";
117
118
119 public static final String ORIENTATION_PROPERTY = MultiThumbSlider.class.getName() + ".orientation";
120
121
122
123
124
125 public static final String VALUES_PROPERTY = MultiThumbSlider.class.getName() + ".values";
126
127
128 public static final String ADJUST_PROPERTY = MultiThumbSlider.class.getName() + ".adjusting";
129
130
131 public static final String PAINT_TICKS_PROPERTY = MultiThumbSlider.class.getName() + ".paint ticks";
132
133
134 protected float[] thumbPositions = new float[0];
135
136
137 protected T[] values;
138
139
140 List<ChangeListener> changeListeners;
141
142
143
144
145 public static final int HORIZONTAL = SwingConstants.HORIZONTAL;
146
147
148
149
150 public static final int VERTICAL = SwingConstants.VERTICAL;
151
152
153
154
155
156
157 public MultiThumbSlider(float[] thumbPositions, T[] values)
158 {
159 this(HORIZONTAL, thumbPositions, values);
160 }
161
162
163
164
165
166
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
230
231 public void setUI(MultiThumbSliderUi<T> ui)
232 {
233 super.setUI((ComponentUI) ui);
234 }
235
236
237
238
239
240
241
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
254
255
256 public void removeChangeListener(ChangeListener l)
257 {
258 if (this.changeListeners == null)
259 return;
260 this.changeListeners.remove(l);
261 }
262
263
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
283
284
285 @Override
286 public void transferFocus()
287 {
288 transferFocus(true);
289 }
290
291
292
293
294
295
296 private void transferFocus(boolean forward)
297 {
298 int direction = (forward) ? 1 : -1;
299
300
301 if (getOrientation() == VERTICAL)
302 direction = direction * -1;
303
304
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
337
338
339 @Override
340 public void transferFocusBackward()
341 {
342 transferFocus(false);
343 }
344
345
346
347
348
349
350
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
360
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
378
379
380
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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 public boolean doDoubleClick(int x, int y)
405 {
406 return false;
407 }
408
409
410
411
412
413
414
415
416
417
418
419 public boolean doPopup(int x, int y)
420 {
421 return false;
422 }
423
424
425
426
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
438
439
440
441
442 public void setPaintTicks(boolean b)
443 {
444 putClientProperty(PAINT_TICKS_PROPERTY, b);
445 }
446
447
448
449
450
451
452
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
512
513
514
515
516
517
518
519 public void setValueIsAdjusting(boolean b)
520 {
521 putClientProperty(ADJUST_PROPERTY, b);
522 }
523
524
525
526
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
538
539
540
541
542
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
553
554
555
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
566
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
586
587
588
589
590
591
592
593
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
614
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;
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
652
653
654 public int getThumbCount()
655 {
656 return this.thumbPositions.length;
657 }
658
659
660
661
662
663
664
665
666
667 public void setSelectedThumb(int index)
668 {
669 putClientProperty(SELECTED_THUMB_PROPERTY, new Integer(index));
670 }
671
672
673
674
675
676 public int getSelectedThumb()
677 {
678 return getSelectedThumb(true);
679 }
680
681
682
683
684
685
686
687
688
689
690
691
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
705
706
707 public void setAutoAdding(boolean b)
708 {
709 putClientProperty(AUTOADD_PROPERTY, b);
710 }
711
712
713
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
725
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
737
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
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
759
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 }