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 }