1 package org.djutils.swing.multislider;
2
3 import java.util.List;
4
5 import org.djutils.exceptions.Throw;
6 import org.djutils.immutablecollections.ImmutableArrayList;
7 import org.djutils.immutablecollections.ImmutableList;
8
9 /**
10 * CategorialMultiSlider implements a slider with multiple thumbs and categorial values. The slider returns instances of a given
11 * type. The MultiSlider is implemented by drawing a number of sliders on top of each other using an Swing
12 * {@code OverlayManager}, and passing the mouse events from a glass pane on top to the correct slider(s). The class is a
13 * {@code ChangeListener} to listen to the changes of individual sliders underneath.
14 * <p>
15 * Several models exist to indicate whether thumbs can pass each other or not, or be on top of each other or not.
16 * </p>
17 * <p>
18 * Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
19 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
20 * distributed under a three-clause BSD-style license, which can be found at
21 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
22 * </p>
23 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
24 * @param <T> The type of the labels for the multislider
25 */
26 public class CategorialMultiSlider<T> extends AbstractMultiSlider<T>
27 {
28 /** */
29 private static final long serialVersionUID = 20241124L;
30
31 /** the categorial scale of this multislider. */
32 private final ImmutableList<T> scale;
33
34 /**
35 * Creates a horizontal slider using the specified scale and initial values.
36 * @param scale the list of categorial values for the scale
37 * @param initialValues the initial values of the slider.
38 * @throws IllegalArgumentException if the initial values are not part of the scale, or if the number of thumbs is 0, or
39 * when the values are not in increasing scale order (which is important for restricting passing and overlap),
40 * or when the scale has duplicate values
41 */
42 @SafeVarargs
43 public CategorialMultiSlider(final List<T> scale, final T... initialValues)
44 {
45 this(true, scale, initialValues);
46 }
47
48 /**
49 * Creates a horizontal or vertical slider using the specified min, max and initial values.
50 * @param horizontal the orientation of the slider; true for horizontal, false for vertical
51 * @param scale the list of categorial values for the scale
52 * @param initialValues the initial values of the slider.
53 * @throws IllegalArgumentException if the initial values are not part of the scale, or if the number of thumbs is 0, or
54 * when the values are not in increasing scale order (which is important for restricting passing and overlap),
55 * or when the scale has duplicate values
56 */
57 @SafeVarargs
58 public CategorialMultiSlider(final boolean horizontal, final List<T> scale, final T... initialValues)
59 {
60 super(0, scale.size() - 1, horizontal, intArray(scale, initialValues));
61 this.scale = new ImmutableArrayList<T>(scale);
62 setLabelTable(createStandardLabels(1));
63 }
64
65 /**
66 * Make an int array from the initial values given the scale.
67 * @param scale the list of categorial values for the scale
68 * @param initialValues the initial values of the slider.
69 * @param <T> the type of objects for the categorial scale
70 * @return an int array with the index vales of the initial values on the scale
71 * @throws IllegalArgumentException if the initial values are not part of the scale, or when the scale has duplicate values
72 */
73 @SafeVarargs
74 private static <T> int[] intArray(final List<T> scale, final T... initialValues)
75 {
76 int[] ret = new int[initialValues.length];
77 Throw.when(initialValues.length == 0, IllegalArgumentException.class, "the number of thumbs cannot be zero");
78
79 // check for duplicate values in the scale
80 for (int i = 0; i < scale.size(); i++)
81 {
82 for (int j = 0; j < scale.size(); j++)
83 {
84 if (i != j && scale.get(i).equals(scale.get(j)))
85 {
86 throw new IllegalArgumentException(
87 "Two values on the CategorialMultiSlider scale are the same: " + scale.get(i));
88 }
89 }
90 }
91
92 // create the indices
93 for (int i = 0; i < initialValues.length; i++)
94 {
95 int index = scale.indexOf(initialValues[i]);
96 if (index == -1)
97 {
98 throw new IllegalArgumentException(
99 "Initial value " + initialValues[i] + " not found on the CategorialMultiSlider scale: " + scale);
100 }
101 ret[i] = index;
102 }
103
104 return ret;
105 }
106
107 /** {@inheritDoc} */
108 @Override
109 protected T mapIndexToValue(final int index)
110 {
111 Throw.when(index < 0 || index >= this.scale.size(), IllegalArgumentException.class,
112 "CategorialMultiSlider scale: index < 0 || index > scale size");
113 return this.scale.get(index);
114 }
115
116 /** {@inheritDoc} */
117 @Override
118 protected int mapValueToIndex(final T value)
119 {
120 int index = this.scale.indexOf(value);
121 if (index == -1)
122 {
123 throw new IllegalArgumentException(
124 "Value " + value + " not found on the CategorialMultiSlider scale: " + this.scale);
125 }
126 return index;
127 }
128
129 }