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