1 package org.djutils.swing.multislider;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.Font;
7 import java.awt.Graphics;
8 import java.awt.Point;
9 import java.awt.event.ComponentAdapter;
10 import java.awt.event.ComponentEvent;
11 import java.awt.event.MouseEvent;
12 import java.awt.event.MouseListener;
13 import java.awt.event.MouseMotionAdapter;
14 import java.util.Dictionary;
15 import java.util.HashMap;
16 import java.util.Hashtable;
17 import java.util.Map;
18
19 import javax.swing.BoxLayout;
20 import javax.swing.JComponent;
21 import javax.swing.JLabel;
22 import javax.swing.JPanel;
23 import javax.swing.JSlider;
24 import javax.swing.OverlayLayout;
25 import javax.swing.SwingConstants;
26 import javax.swing.SwingUtilities;
27 import javax.swing.event.ChangeEvent;
28 import javax.swing.event.ChangeListener;
29 import javax.swing.plaf.SliderUI;
30 import javax.swing.plaf.basic.BasicSliderUI;
31
32 import org.djutils.exceptions.Throw;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public abstract class AbstractMultiSlider<T> extends JComponent
56 {
57
58 private static final long serialVersionUID = 1L;
59
60
61 private JSlider[] sliders;
62
63
64 private transient int busySlider = -1;
65
66
67 private transient boolean busy = false;
68
69
70 private final DispatcherPane dispatcherPane;
71
72
73 private final LabelPanel labelPanel;
74
75
76 private final int[] initialIndexValues;
77
78
79 private final int[] lastIndexValues;
80
81
82 private final Map<Integer, String> thumbLabels = new HashMap<>();
83
84
85 private boolean drawThumbLabels = false;
86
87
88 private int trackSizeLoPx;
89
90
91 private int trackSizeHiPx;
92
93
94 private boolean passing = false;
95
96
97 private boolean overlap = true;
98
99
100 private boolean testingStateChange = false;
101
102
103
104
105
106
107 private transient ChangeEvent changeEvent = null;
108
109
110
111
112
113
114
115
116
117
118
119 public AbstractMultiSlider(final int minIndex, final int maxIndex, final boolean horizontal,
120 final int... initialIndexValues)
121 {
122 Throw.when(initialIndexValues.length == 0, IllegalArgumentException.class, "the number of thumbs cannot be zero");
123 Throw.when(minIndex >= maxIndex, IllegalArgumentException.class, "min should be less than max");
124 for (int i = 0; i < initialIndexValues.length; i++)
125 {
126 Throw.when(initialIndexValues[i] < minIndex || initialIndexValues[i] > maxIndex, IllegalArgumentException.class,
127 "all initial value should be between min and max (inclusive)");
128 Throw.when(i > 0 && initialIndexValues[i] < initialIndexValues[i - 1], IllegalArgumentException.class,
129 "all initial value should be in increasing order or overlap");
130 }
131
132
133 this.initialIndexValues = new int[initialIndexValues.length];
134 this.lastIndexValues = new int[initialIndexValues.length];
135
136
137 setLayout(new BorderLayout());
138 JPanel topPanel = new JPanel();
139 topPanel.setLayout(new BoxLayout(topPanel, horizontal ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS));
140 topPanel.setOpaque(false);
141 add(topPanel, horizontal ? BorderLayout.NORTH : BorderLayout.WEST);
142
143
144 this.labelPanel = new LabelPanel(this);
145 this.labelPanel.setLayout(new BorderLayout());
146 this.labelPanel.setPreferredSize(new Dimension(1000, 0));
147 this.labelPanel.setOpaque(false);
148 topPanel.add(this.labelPanel);
149
150
151 JPanel sliderPanel = new JPanel();
152 sliderPanel.setOpaque(false);
153 topPanel.add(sliderPanel);
154
155
156 this.dispatcherPane = new DispatcherPane(this);
157 sliderPanel.add(this.dispatcherPane);
158
159
160 sliderPanel.setLayout(new OverlayLayout(sliderPanel));
161 this.sliders = new JSlider[initialIndexValues.length];
162 for (int i = initialIndexValues.length - 1; i >= 0; i--)
163 {
164 this.initialIndexValues[i] = initialIndexValues[i];
165 this.lastIndexValues[i] = initialIndexValues[i];
166 var slider = new JSlider(horizontal ? SwingConstants.HORIZONTAL : SwingConstants.VERTICAL, minIndex, maxIndex,
167 initialIndexValues[i]);
168 this.sliders[i] = slider;
169 slider.setOpaque(false);
170 slider.setPaintTrack(i == 0);
171 sliderPanel.add(slider);
172
173
174 slider.setValue(initialIndexValues[i]);
175
176
177 this.thumbLabels.put(i, "");
178 }
179
180 this.dispatcherPane.setPreferredSize(new Dimension(this.sliders[0].getSize()));
181 calculateTrackSize();
182
183
184 addComponentListener(new ComponentAdapter()
185 {
186 @Override
187 public void componentResized(final ComponentEvent e)
188 {
189 super.componentResized(e);
190 AbstractMultiSlider.this.setUI(getUI());
191 calculateTrackSize();
192 AbstractMultiSlider.this.dispatcherPane.revalidate();
193 AbstractMultiSlider.this.labelPanel.revalidate();
194 AbstractMultiSlider.this.labelPanel.repaint();
195 }
196 });
197
198
199 for (int i = 0; i < this.sliders.length; i++)
200 {
201 final int index = i;
202 this.sliders[index].addChangeListener(new ChangeListener()
203 {
204 @Override
205 public void stateChanged(final ChangeEvent e)
206 {
207 if (!AbstractMultiSlider.this.testingStateChange)
208 {
209 AbstractMultiSlider.this.testingStateChange = true;
210 checkRestrictions(index);
211 AbstractMultiSlider.this.testingStateChange = false;
212 }
213 AbstractMultiSlider.this.fireStateChanged();
214 }
215 });
216 }
217
218 invalidate();
219 }
220
221
222
223
224
225 protected JSlider[] getSliders()
226 {
227 return this.sliders;
228 }
229
230
231
232
233
234
235 public JSlider getSlider(final int i)
236 {
237 return this.sliders[i];
238 }
239
240
241
242
243
244 public int getNumberOfThumbs()
245 {
246 return this.sliders.length;
247 }
248
249
250
251
252
253 protected void setBusySlider(final int i)
254 {
255 this.busySlider = i;
256 }
257
258
259
260
261
262
263 protected boolean isBusySlider(final int i)
264 {
265 return this.busySlider == i;
266 }
267
268
269
270
271
272 protected int getBusySlider()
273 {
274 return this.busySlider;
275 }
276
277
278
279
280
281
282
283 public boolean isBusy()
284 {
285 return this.busy;
286 }
287
288
289
290
291
292
293
294 protected void setBusy(final boolean busy)
295 {
296 this.busy = busy;
297 }
298
299
300
301
302 public void resetToInitialValues()
303 {
304 for (int i = 0; i < this.sliders.length; i++)
305 {
306 this.sliders[i].setValue(this.initialIndexValues[i]);
307 this.sliders[i].invalidate();
308 this.sliders[i].repaint();
309 }
310 fireFinalValueChanged();
311 invalidate();
312 repaint();
313 }
314
315
316
317
318
319 protected LabelPanel getLabelPanel()
320 {
321 return this.labelPanel;
322 }
323
324
325
326
327
328
329
330 public void setThumbLabel(final int i, final String label)
331 {
332 Throw.when(i < 0 || i >= getNumberOfThumbs(), IndexOutOfBoundsException.class, "thumb number %d is out of bounds", i);
333 this.thumbLabels.put(i, label);
334 invalidate();
335 }
336
337
338
339
340
341
342
343 public String getThumbLabel(final int i)
344 {
345 Throw.when(i < 0 || i >= getNumberOfThumbs(), IndexOutOfBoundsException.class, "thumb number %d is out of bounds", i);
346 return this.thumbLabels.get(i);
347 }
348
349
350
351
352
353
354 public void setDrawThumbLabels(final boolean b, final int sizePx)
355 {
356 calculateTrackSize();
357 JSlider js = getSlider(0);
358 this.drawThumbLabels = b;
359 if (b)
360 {
361 if (isHorizontal())
362 {
363 this.labelPanel.setPreferredSize(new Dimension(js.getWidth(), sizePx));
364 }
365 else
366 {
367 this.labelPanel.setPreferredSize(new Dimension(sizePx, js.getHeight()));
368 }
369 }
370 else
371 {
372 if (isHorizontal())
373 {
374 this.labelPanel.setPreferredSize(new Dimension(js.getWidth(), 0));
375 }
376 else
377 {
378 this.labelPanel.setPreferredSize(new Dimension(0, js.getHeight()));
379 }
380 }
381 this.labelPanel.revalidate();
382 revalidate();
383 }
384
385
386
387
388
389 public boolean isDrawThumbLabels()
390 {
391 return this.drawThumbLabels;
392 }
393
394
395
396
397 protected void calculateTrackSize()
398 {
399 JSlider js = getSlider(0);
400 BasicSliderUI ui = (BasicSliderUI) js.getUI();
401 int loValue = getInverted() ? js.getMaximum() : js.getMinimum();
402 int hiValue = getInverted() ? js.getMinimum() : js.getMaximum();
403 if (isHorizontal())
404 {
405 this.trackSizeLoPx = 0;
406 this.trackSizeHiPx = js.getWidth();
407 int i = 0;
408 while (i < js.getWidth() && ui.valueForXPosition(i) == loValue)
409 {
410 this.trackSizeLoPx = i++;
411 }
412 i = js.getWidth();
413 while (i >= 0 && ui.valueForXPosition(i) == hiValue)
414 {
415 this.trackSizeHiPx = i--;
416 }
417 }
418 else
419 {
420 this.trackSizeLoPx = 0;
421 this.trackSizeHiPx = js.getHeight();
422 int i = 0;
423 while (i < js.getHeight() && ui.valueForYPosition(i) == hiValue)
424 {
425 this.trackSizeLoPx = i++;
426 }
427 i = js.getHeight();
428 while (i >= 0 && ui.valueForYPosition(i) == loValue)
429 {
430 this.trackSizeHiPx = i--;
431 }
432 }
433
434
435 int nr = getIndexMaximum() - getIndexMinimum();
436 int pxPerNr = (this.trackSizeHiPx - this.trackSizeLoPx) / nr;
437 this.trackSizeLoPx = isHorizontal() ? this.trackSizeLoPx - pxPerNr / 2 : this.trackSizeLoPx + pxPerNr / 2;
438 this.trackSizeHiPx = isHorizontal() ? this.trackSizeHiPx + pxPerNr / 2 : this.trackSizeHiPx - pxPerNr / 2;
439 }
440
441
442
443
444
445 protected int trackSize()
446 {
447
448 if (this.trackSizeHiPx - this.trackSizeLoPx < 2)
449 {
450 calculateTrackSize();
451 }
452 return this.trackSizeHiPx - this.trackSizeLoPx;
453 }
454
455
456
457
458
459
460 protected int thumbPositionPx(final int i)
461 {
462 JSlider slider = getSlider(i);
463 int value = slider.getValue();
464 int min = slider.getMinimum();
465 int max = slider.getMaximum();
466 int ts = trackSize();
467 if (getInverted())
468 {
469 return this.trackSizeLoPx + (int) (1.0 * ts * (max - value) / (max - min));
470 }
471 return this.trackSizeLoPx + (int) (1.0 * ts * (value - min) / (max - min));
472 }
473
474
475
476
477
478 protected DispatcherPane getDispatcherPane()
479 {
480 return this.dispatcherPane;
481 }
482
483
484
485
486
487 @Override
488 public SliderUI getUI()
489 {
490 return this.sliders[0].getUI();
491 }
492
493
494
495
496
497 public void setUI(final SliderUI ui)
498 {
499 for (var slider : this.sliders)
500 {
501 try
502 {
503 slider.setUI(ui.getClass().getDeclaredConstructor().newInstance());
504 }
505 catch (Exception exception)
506 {
507
508 }
509 }
510 invalidate();
511 calculateTrackSize();
512 }
513
514
515
516
517 @Override
518 public void updateUI()
519 {
520 for (var slider : this.sliders)
521 {
522 slider.updateUI();
523 }
524 invalidate();
525 calculateTrackSize();
526 }
527
528
529
530
531
532 @Override
533 public String getUIClassID()
534 {
535 return this.sliders[0].getUIClassID();
536 }
537
538
539
540
541
542 public void addChangeListener(final ChangeListener listener)
543 {
544 this.listenerList.add(ChangeListener.class, listener);
545 }
546
547
548
549
550
551 public void removeChangeListener(final ChangeListener listener)
552 {
553 this.listenerList.remove(ChangeListener.class, listener);
554 }
555
556
557
558
559
560 public ChangeListener[] getChangeListeners()
561 {
562 return this.listenerList.getListeners(ChangeListener.class);
563 }
564
565
566
567
568
569
570
571
572
573 protected void fireStateChanged()
574 {
575
576 boolean changed = false;
577 for (int i = 0; i < getNumberOfThumbs(); i++)
578 {
579 if (this.lastIndexValues[i] != this.sliders[i].getValue())
580 {
581 changed = true;
582 this.lastIndexValues[i] = this.sliders[i].getValue();
583 }
584 }
585
586 if (changed)
587 {
588
589 Object[] listeners = this.listenerList.getListenerList();
590 for (int i = listeners.length - 2; i >= 0; i -= 2)
591 {
592 if (listeners[i] == ChangeListener.class)
593 {
594 if (this.changeEvent == null)
595 {
596 this.changeEvent = new ChangeEvent(this);
597 }
598 ((ChangeListener) listeners[i + 1]).stateChanged(this.changeEvent);
599 }
600 }
601 }
602 }
603
604
605
606
607
608 public void addFinalValueChangeListener(final FinalValueChangeListener listener)
609 {
610 this.listenerList.add(FinalValueChangeListener.class, listener);
611 }
612
613
614
615
616
617 public void removeFinalValueChangeListener(final FinalValueChangeListener listener)
618 {
619 this.listenerList.remove(FinalValueChangeListener.class, listener);
620 }
621
622
623
624
625
626
627 public FinalValueChangeListener[] getFinalValueChangeListeners()
628 {
629 return this.listenerList.getListeners(FinalValueChangeListener.class);
630 }
631
632
633
634
635
636
637
638
639
640
641 protected void fireFinalValueChanged()
642 {
643
644 Object[] listeners = this.listenerList.getListenerList();
645 for (int i = listeners.length - 2; i >= 0; i -= 2)
646 {
647 if (listeners[i] == FinalValueChangeListener.class)
648 {
649 if (this.changeEvent == null)
650 {
651 this.changeEvent = new ChangeEvent(this);
652 }
653 ((FinalValueChangeListener) listeners[i + 1]).stateChanged(this.changeEvent);
654 }
655 }
656 }
657
658
659
660
661
662
663 protected abstract T mapIndexToValue(int index);
664
665
666
667
668
669
670
671 protected abstract int mapValueToIndex(T value);
672
673
674
675
676
677
678
679 public T getValue(final int i)
680 {
681 return mapIndexToValue(getIndexValue(i));
682 }
683
684
685
686
687
688
689
690 public void setValue(final int i, final T value)
691 {
692 int n = mapValueToIndex(value);
693 setIndexValue(i, n);
694 }
695
696
697
698
699
700
701 public int getIndexValue(final int i)
702 {
703 return this.sliders[i].getModel().getValue();
704 }
705
706
707
708
709
710
711
712
713
714
715
716 protected void setIndexValue(final int i, final int n)
717 {
718 Throw.when(n < getIndexMinimum() || n > getIndexMaximum(), IllegalArgumentException.class,
719 "setValue(%d) not in range [%d, %d]", n, getIndexMinimum(), getIndexMaximum());
720 this.sliders[i].setValue(n);
721 checkRestrictions(i);
722 this.sliders[i].invalidate();
723 this.sliders[i].repaint();
724 fireFinalValueChanged();
725 }
726
727
728
729
730
731 protected int getIndexMinimum()
732 {
733 return this.sliders[0].getMinimum();
734 }
735
736
737
738
739
740
741
742
743
744
745
746 protected void setIndexMinimum(final int minimum)
747 {
748 Throw.when(minimum >= getIndexMaximum(), IllegalArgumentException.class, "setMinimum(%d) >= maximum %d", minimum,
749 getIndexMaximum());
750 int oldMin = getIndexMinimum();
751 for (var slider : this.sliders)
752 {
753 slider.setMinimum(minimum);
754 }
755 checkRestrictions();
756 for (var slider : this.sliders)
757 {
758 slider.invalidate();
759 }
760 invalidate();
761 fireFinalValueChanged();
762 firePropertyChange("minimum", Integer.valueOf(oldMin), Integer.valueOf(minimum));
763 calculateTrackSize();
764 }
765
766
767
768
769
770 public T getMinimum()
771 {
772 return mapIndexToValue(getIndexMinimum());
773 }
774
775
776
777
778
779 public void setMinimum(final T minimum)
780 {
781 setIndexMinimum(mapValueToIndex(minimum));
782 }
783
784
785
786
787
788 protected int getIndexMaximum()
789 {
790 return this.sliders[0].getMaximum();
791 }
792
793
794
795
796
797
798
799
800
801
802
803 protected void setIndexMaximum(final int maximum)
804 {
805 Throw.when(maximum <= getIndexMinimum(), IllegalArgumentException.class, "setMaximum(%d) >= minimum %d", maximum,
806 getIndexMinimum());
807 int oldMax = getIndexMaximum();
808 for (var slider : this.sliders)
809 {
810 slider.setMaximum(maximum);
811 }
812 checkRestrictions();
813 for (var slider : this.sliders)
814 {
815 slider.invalidate();
816 }
817 fireFinalValueChanged();
818 firePropertyChange("maximum", Integer.valueOf(oldMax), Integer.valueOf(maximum));
819 invalidate();
820 calculateTrackSize();
821 }
822
823
824
825
826
827 public T getMaximum()
828 {
829 return mapIndexToValue(getIndexMaximum());
830 }
831
832
833
834
835
836 public void setMaximum(final T maximum)
837 {
838 setIndexMaximum(mapValueToIndex(maximum));
839 }
840
841
842
843
844
845 public int getExtent()
846 {
847 return this.sliders[0].getExtent();
848 }
849
850
851
852
853
854
855
856
857
858
859
860
861 public void setExtent(final int extent)
862 {
863 for (var slider : this.sliders)
864 {
865 slider.setExtent(extent);
866 }
867 }
868
869
870
871
872
873 public int getOrientation()
874 {
875 return this.sliders[0].getOrientation();
876 }
877
878
879
880
881
882 public boolean isHorizontal()
883 {
884 return getOrientation() == SwingConstants.HORIZONTAL;
885 }
886
887
888
889
890
891 public boolean isVertical()
892 {
893 return !isHorizontal();
894 }
895
896
897
898
899
900
901 public void setOrientation(final int orientation)
902 {
903 for (var slider : this.sliders)
904 {
905 slider.setOrientation(orientation);
906 slider.invalidate();
907 }
908 this.dispatcherPane.setPreferredSize(new Dimension(this.sliders[0].getSize()));
909 invalidate();
910 calculateTrackSize();
911 }
912
913 @Override
914 public void setFont(final Font font)
915 {
916 for (var slider : this.sliders)
917 {
918 slider.setFont(font);
919 slider.invalidate();
920 }
921 invalidate();
922 calculateTrackSize();
923 }
924
925
926
927
928
929 @SuppressWarnings("rawtypes")
930 public Dictionary getLabelTable()
931 {
932 return this.sliders[0].getLabelTable();
933 }
934
935
936
937
938
939
940
941 @SuppressWarnings("rawtypes")
942 public void setLabelTable(final Dictionary labels)
943 {
944 for (var slider : this.sliders)
945 {
946 slider.setLabelTable(labels);
947 slider.invalidate();
948 }
949 invalidate();
950 calculateTrackSize();
951 }
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966 public Hashtable<Integer, JComponent> createStandardLabels(final int increment)
967 {
968 return createStandardLabels(increment, getIndexMinimum());
969 }
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986 public Hashtable<Integer, JComponent> createStandardLabels(final int increment, final int startIndex)
987 {
988 Throw.when(increment <= 0, IllegalArgumentException.class, "increment should be > 0");
989 Throw.when(startIndex < getIndexMinimum() || startIndex > getIndexMaximum(), IllegalArgumentException.class,
990 "startIndex should be between minimum index and maximum index");
991 Hashtable<Integer, JComponent> labels = new Hashtable<>();
992 for (int i = startIndex; i <= getIndexMaximum(); i += increment)
993 {
994 labels.put(i, new JLabel(format(mapIndexToValue(i))));
995 }
996 return labels;
997 }
998
999
1000
1001
1002
1003
1004
1005 protected String format(final T value)
1006 {
1007 return value.toString();
1008 }
1009
1010
1011
1012
1013
1014 public boolean getInverted()
1015 {
1016 return this.sliders[0].getInverted();
1017 }
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029 public void setInverted(final boolean b)
1030 {
1031 for (var slider : this.sliders)
1032 {
1033 slider.setInverted(b);
1034 slider.invalidate();
1035 }
1036 invalidate();
1037 calculateTrackSize();
1038 }
1039
1040
1041
1042
1043
1044
1045
1046 public int getMajorTickSpacing()
1047 {
1048 return this.sliders[0].getMajorTickSpacing();
1049 }
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064 public void setMajorTickSpacing(final int n)
1065 {
1066 int oldValue = getMajorTickSpacing();
1067 for (var slider : this.sliders)
1068 {
1069 slider.setMajorTickSpacing(n);
1070 slider.invalidate();
1071 }
1072 firePropertyChange("majorTickSpacing", oldValue, n);
1073 invalidate();
1074 }
1075
1076
1077
1078
1079
1080
1081
1082 public int getMinorTickSpacing()
1083 {
1084 return this.sliders[0].getMinorTickSpacing();
1085 }
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097 public void setMinorTickSpacing(final int n)
1098 {
1099 int oldValue = getMinorTickSpacing();
1100 for (var slider : this.sliders)
1101 {
1102 slider.setMinorTickSpacing(n);
1103 slider.invalidate();
1104 }
1105 firePropertyChange("minorTickSpacing", oldValue, n);
1106 invalidate();
1107 }
1108
1109
1110
1111
1112
1113
1114 public boolean getSnapToTicks()
1115 {
1116 return this.sliders[0].getSnapToTicks();
1117 }
1118
1119
1120
1121
1122
1123
1124 public void setSnapToTicks(final boolean b)
1125 {
1126 boolean oldValue = getSnapToTicks();
1127 for (var slider : this.sliders)
1128 {
1129 slider.setSnapToTicks(b);
1130 }
1131 firePropertyChange("snapToTicks", oldValue, b);
1132 }
1133
1134
1135
1136
1137
1138 public boolean getPaintTicks()
1139 {
1140 return this.sliders[0].getPaintTicks();
1141 }
1142
1143
1144
1145
1146
1147 public void setPaintTicks(final boolean b)
1148 {
1149 boolean oldValue = getPaintTicks();
1150 for (var slider : this.sliders)
1151 {
1152 slider.setPaintTicks(b);
1153 slider.invalidate();
1154 }
1155 firePropertyChange("paintTicks", oldValue, b);
1156 invalidate();
1157 calculateTrackSize();
1158 }
1159
1160
1161
1162
1163
1164 public boolean getPaintTrack()
1165 {
1166 return this.sliders[0].getPaintTrack();
1167 }
1168
1169
1170
1171
1172
1173
1174
1175 public void setPaintTrack(final boolean b)
1176 {
1177 boolean oldValue = getPaintTrack();
1178 this.sliders[0].setPaintTrack(b);
1179 firePropertyChange("paintTrack", oldValue, b);
1180 calculateTrackSize();
1181 }
1182
1183
1184
1185
1186
1187 public boolean getPaintLabels()
1188 {
1189 return this.sliders[0].getPaintLabels();
1190 }
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202 public void setPaintLabels(final boolean b)
1203 {
1204 boolean oldValue = getPaintLabels();
1205 for (var slider : this.sliders)
1206 {
1207 slider.setPaintLabels(b);
1208 slider.invalidate();
1209 }
1210 firePropertyChange("paintLabels", oldValue, b);
1211 invalidate();
1212 calculateTrackSize();
1213 }
1214
1215
1216
1217
1218
1219 public void setPassing(final boolean b)
1220 {
1221 this.passing = b;
1222 if (!this.passing)
1223 {
1224 checkRestrictions();
1225 }
1226 }
1227
1228
1229
1230
1231
1232 public boolean getPassing()
1233 {
1234 return this.passing;
1235 }
1236
1237
1238
1239
1240
1241 public void setOverlap(final boolean b)
1242 {
1243 this.overlap = b;
1244 if (!this.overlap)
1245 {
1246 checkRestrictions();
1247 }
1248 }
1249
1250
1251
1252
1253
1254 public boolean getOverlap()
1255 {
1256 return this.overlap;
1257 }
1258
1259
1260
1261
1262
1263 protected boolean checkRestrictions()
1264 {
1265 boolean ret = true;
1266 if (!getPassing())
1267 {
1268 for (int i = 1; i < getNumberOfThumbs(); i++)
1269 {
1270
1271 if (getIndexValue(i) <= getIndexValue(i - 1))
1272 {
1273 getSlider(i).setValue(getIndexValue(i - 1));
1274 ret = false;
1275 }
1276 }
1277 for (int i = getNumberOfThumbs() - 1; i >= 1; i--)
1278 {
1279
1280 if (getIndexValue(i) <= getIndexValue(i - 1))
1281 {
1282 getSlider(i - 1).setValue(getIndexValue(i));
1283 ret = false;
1284 }
1285 }
1286 }
1287 if (!getOverlap())
1288 {
1289 for (int i = 1; i < getNumberOfThumbs(); i++)
1290 {
1291
1292 if (getIndexValue(i) <= getIndexValue(i - 1))
1293 {
1294 getSlider(i).setValue(getIndexValue(i - 1) + 1);
1295 ret = false;
1296 }
1297 }
1298 for (int i = getNumberOfThumbs() - 1; i >= 1; i--)
1299 {
1300
1301 if (getIndexValue(i) <= getIndexValue(i - 1))
1302 {
1303 getSlider(i - 1).setValue(getIndexValue(i) - 1);
1304 ret = false;
1305 }
1306 }
1307 }
1308 if (!ret)
1309 {
1310 invalidate();
1311 repaint();
1312 }
1313 return ret;
1314 }
1315
1316
1317
1318
1319
1320
1321 protected boolean checkRestrictions(final int index)
1322 {
1323 boolean ret = true;
1324 if (!getPassing())
1325 {
1326 if (index > 0 && getIndexValue(index) <= getIndexValue(index - 1))
1327 {
1328 getSlider(index).setValue(getIndexValue(index - 1));
1329 ret = false;
1330 }
1331 if (index < getNumberOfThumbs() - 1 && getIndexValue(index) >= getIndexValue(index + 1))
1332 {
1333 getSlider(index).setValue(getIndexValue(index + 1));
1334 ret = false;
1335 }
1336 }
1337 if (!getOverlap())
1338 {
1339 if (index > 0 && getIndexValue(index) <= getIndexValue(index - 1))
1340 {
1341 getSlider(index).setValue(getIndexValue(index - 1) + 1);
1342 ret = false;
1343 }
1344 if (index < getNumberOfThumbs() - 1 && getIndexValue(index) >= getIndexValue(index + 1))
1345 {
1346 getSlider(index).setValue(getIndexValue(index + 1) - 1);
1347 ret = false;
1348 }
1349 }
1350 if (!ret)
1351 {
1352 getSlider(index).invalidate();
1353 getSlider(index).repaint();
1354 }
1355 return ret;
1356 }
1357
1358
1359
1360
1361
1362
1363 protected static class DispatcherPane extends JComponent
1364 {
1365
1366 private static final long serialVersionUID = 1L;
1367
1368
1369 @SuppressWarnings("checkstyle:visibilitymodifier")
1370 protected final AbstractMultiSlider<?> multiSlider;
1371
1372
1373
1374
1375
1376
1377
1378
1379 int closestSliderIndex(final Point p)
1380 {
1381 if (this.multiSlider.getBusySlider() >= 0)
1382 {
1383 return this.multiSlider.getBusySlider();
1384 }
1385
1386 int mindist = Integer.MAX_VALUE;
1387 int minIndex = -1;
1388 int mini = -1;
1389 int loi = Integer.MAX_VALUE;
1390 int hii = -1;
1391 for (int i = 0; i < this.multiSlider.getNumberOfThumbs(); i++)
1392 {
1393 int posPx = this.multiSlider.thumbPositionPx(i);
1394 int dist = this.multiSlider.isHorizontal() ? posPx - p.x : posPx - (getHeight() - p.y);
1395 if (Math.abs(dist) == Math.abs(mindist))
1396 {
1397 hii = i;
1398 }
1399 else if (Math.abs(dist) < Math.abs(mindist))
1400 {
1401 mindist = dist;
1402 mini = i;
1403 minIndex = this.multiSlider.getIndexValue(i);
1404 loi = i;
1405 hii = i;
1406 }
1407 }
1408
1409
1410 if (loi == hii || this.multiSlider.getPassing())
1411 {
1412 return mini;
1413 }
1414 if (minIndex == this.multiSlider.getIndexMinimum())
1415 {
1416 return hii;
1417 }
1418 if (minIndex == this.multiSlider.getIndexMaximum())
1419 {
1420 return loi;
1421 }
1422 if (mindist > 0)
1423 {
1424 return loi;
1425 }
1426 return hii;
1427 }
1428
1429
1430
1431
1432
1433
1434 int dispatch(final MouseEvent e, final boolean always)
1435 {
1436 var slider = DispatcherPane.this.multiSlider.getSlider(0);
1437 Point pSlider = SwingUtilities.convertPoint(DispatcherPane.this, e.getPoint(), slider);
1438 if (always || (pSlider.x >= 0 && pSlider.x <= slider.getSize().width && pSlider.y >= 0
1439 && pSlider.y <= slider.getSize().height))
1440 {
1441 int index = closestSliderIndex(pSlider);
1442 MouseEvent meSlider = new MouseEvent((Component) e.getSource(), e.getID(), e.getWhen(), e.getModifiersEx(),
1443 pSlider.x, pSlider.y, e.getClickCount(), e.isPopupTrigger(), e.getButton());
1444 try
1445 {
1446 DispatcherPane.this.multiSlider.getSlider(index).dispatchEvent(meSlider);
1447 }
1448 catch (Exception exception)
1449 {
1450 exception.printStackTrace();
1451 System.out.println("error dispatching mouseEvent " + meSlider);
1452 }
1453 setBusySlider(index);
1454 return index;
1455 }
1456 return -1;
1457 }
1458
1459
1460
1461
1462
1463
1464 void setBusySlider(final int index)
1465 {
1466 this.multiSlider.setBusySlider(index);
1467 }
1468
1469
1470
1471
1472
1473
1474
1475 void setBusy(final boolean b)
1476 {
1477 this.multiSlider.setBusy(b);
1478 }
1479
1480
1481
1482
1483
1484 void checkRestrictions(final int index)
1485 {
1486 this.multiSlider.checkRestrictions(index);
1487 }
1488
1489
1490
1491
1492
1493 public DispatcherPane(final AbstractMultiSlider<?> multiSlider)
1494 {
1495 this.multiSlider = multiSlider;
1496 setOpaque(false);
1497
1498 addMouseListener(new MouseListener()
1499 {
1500 @Override
1501 public void mouseReleased(final MouseEvent e)
1502 {
1503 setBusy(false);
1504 int index = dispatch(e, false);
1505 if (index >= 0)
1506 {
1507 checkRestrictions(index);
1508 DispatcherPane.this.multiSlider.fireFinalValueChanged();
1509 }
1510 setBusySlider(-1);
1511 }
1512
1513 @Override
1514 public void mousePressed(final MouseEvent e)
1515 {
1516 setBusy(true);
1517 int index = dispatch(e, false);
1518 setBusySlider(index);
1519 if (index < 0)
1520 {
1521 setBusy(false);
1522 }
1523 }
1524
1525 @Override
1526 public void mouseExited(final MouseEvent e)
1527 {
1528 setBusy(false);
1529
1530 var ms = DispatcherPane.this.multiSlider;
1531 if (ms.getBusySlider() >= 0)
1532 {
1533 JSlider js = ms.getSlider(ms.getBusySlider());
1534 checkRestrictions(ms.getBusySlider());
1535 MouseEvent meSlider = new MouseEvent(js, MouseEvent.MOUSE_RELEASED, e.getWhen(), 0, 10, 10, 1, false,
1536 MouseEvent.BUTTON1);
1537 js.dispatchEvent(meSlider);
1538 ms.fireFinalValueChanged();
1539 }
1540 setBusySlider(-1);
1541 }
1542
1543 @Override
1544 public void mouseEntered(final MouseEvent e)
1545 {
1546
1547 }
1548
1549 @Override
1550 public void mouseClicked(final MouseEvent e)
1551 {
1552
1553 }
1554 });
1555
1556 addMouseMotionListener(new MouseMotionAdapter()
1557 {
1558 @Override
1559 public void mouseDragged(final MouseEvent e)
1560 {
1561 setBusy(true);
1562 int index = dispatch(e, false);
1563 setBusySlider(index);
1564 if (index < 0)
1565 {
1566 setBusy(false);
1567 }
1568 else
1569 {
1570 checkRestrictions(index);
1571 }
1572 }
1573
1574 @Override
1575 public void mouseMoved(final MouseEvent e)
1576 {
1577
1578
1579 }
1580 });
1581 }
1582 }
1583
1584
1585
1586
1587
1588 protected static class LabelPanel extends JPanel
1589 {
1590
1591 private static final long serialVersionUID = 1L;
1592
1593
1594 private final AbstractMultiSlider<?> multiSlider;
1595
1596
1597
1598
1599
1600 public LabelPanel(final AbstractMultiSlider<?> multiSlider)
1601 {
1602 this.multiSlider = multiSlider;
1603 repaint();
1604 this.multiSlider.addChangeListener(new ChangeListener()
1605 {
1606 @Override
1607 public void stateChanged(final ChangeEvent e)
1608 {
1609 repaint();
1610 }
1611 });
1612 }
1613
1614 @Override
1615 public void paintComponent(final Graphics g)
1616 {
1617 super.paintComponent(g);
1618 if (LabelPanel.this.multiSlider.isDrawThumbLabels())
1619 {
1620 for (int i = 0; i < this.multiSlider.getNumberOfThumbs(); i++)
1621 {
1622 int pos = this.multiSlider.thumbPositionPx(i);
1623 String s = this.multiSlider.getThumbLabel(i);
1624 int sw = g.getFontMetrics().stringWidth(s);
1625 int sh = g.getFontMetrics().getHeight();
1626 if (this.multiSlider.isHorizontal())
1627 {
1628 g.drawString(s, pos - sw / 2, 12);
1629 }
1630 else
1631 {
1632 g.drawString(s, getWidth() - sw - 10, getHeight() - pos + sh / 2 - 3);
1633 }
1634 }
1635 }
1636 }
1637 }
1638
1639
1640
1641
1642
1643 public interface FinalValueChangeListener extends ChangeListener
1644 {
1645
1646 }
1647 }