View Javadoc
1   package org.opentrafficsim.trafficcontrol.trafcod;
2   
3   import java.awt.Color;
4   import java.awt.Dimension;
5   import java.awt.Graphics;
6   import java.awt.Graphics2D;
7   import java.awt.event.MouseEvent;
8   import java.awt.event.MouseListener;
9   import java.awt.event.MouseMotionListener;
10  import java.awt.geom.Point2D;
11  import java.awt.image.BufferedImage;
12  import java.util.LinkedHashSet;
13  import java.util.Optional;
14  import java.util.Set;
15  
16  import javax.swing.JPanel;
17  import javax.swing.ToolTipManager;
18  
19  import org.djutils.event.Event;
20  import org.djutils.event.EventListener;
21  import org.djutils.event.LocalEventProducer;
22  import org.opentrafficsim.base.logger.Logger;
23  import org.opentrafficsim.road.network.lane.object.detector.TrafficLightDetector;
24  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
25  
26  /**
27   * Display the current state of a TrafCOD machine.
28   * <p>
29   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
30   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
31   * </p>
32   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
33   */
34  public class TrafCodDisplay extends JPanel implements MouseMotionListener, MouseListener
35  {
36      /** */
37      private static final long serialVersionUID = 20161115L;
38  
39      /** Background image. */
40      private final BufferedImage image;
41  
42      /** The set of objects drawn on the image. */
43      private Set<TrafCODObject> trafCODObjects = new LinkedHashSet<>();
44  
45      /** Store the tool tip delay so we can restore it when the mouse exits this TrafCODDisplay. */
46      private final int defaultInitialDelay = ToolTipManager.sharedInstance().getInitialDelay();
47  
48      /**
49       * Construct a new TrafCODDisplay.
50       * @param image the background image. This constructor does <b>not</b> make a deep copy of the image. Modifications of the
51       *            image after calling this constructor might have <i>interesting</i> consequences, but should not result in
52       *            crashes.
53       */
54      public TrafCodDisplay(final BufferedImage image)
55      {
56          this.image = image;
57          super.setPreferredSize(new Dimension(this.image.getWidth(), this.image.getHeight()));
58          addMouseMotionListener(this);
59      }
60  
61      /**
62       * Look up a DetectorImage.
63       * @param id id of the DetectorImage
64       * @return the detector image with matching id or empty.
65       */
66      public Optional<DetectorImage> getDetectorImage(final String id)
67      {
68          for (TrafCODObject tco : this.trafCODObjects)
69          {
70              if (tco instanceof DetectorImage && ((DetectorImage) tco).getId().equals(id))
71              {
72                  return Optional.of((DetectorImage) tco);
73              }
74          }
75          return Optional.empty();
76      }
77  
78      @Override
79      protected void paintComponent(final Graphics g)
80      {
81          super.paintComponent(g);
82          g.drawImage(this.image, 0, 0, null);
83          for (TrafCODObject tco : this.trafCODObjects)
84          {
85              tco.draw((Graphics2D) g);
86          }
87      }
88  
89      /**
90       * Add one TrafCODObject to this TrafCODDisplay.
91       * @param trafCODObject the TrafCOD object that must be added
92       */
93      void addTrafCODObject(final TrafCODObject trafCODObject)
94      {
95          this.trafCODObjects.add(trafCODObject);
96      }
97  
98      @Override
99      public void mouseDragged(final MouseEvent e)
100     {
101         mouseMoved(e); // Do the same as in the mouse move event
102     }
103 
104     @Override
105     public void mouseMoved(final MouseEvent e)
106     {
107         String toolTipText = null;
108         for (TrafCODObject tco : this.trafCODObjects)
109         {
110             toolTipText = tco.toolTipHit(e.getX(), e.getY()).orElse(null);
111             if (null != toolTipText)
112             {
113                 break;
114             }
115         }
116         Logger.ots().trace("Setting tool tip text to " + toolTipText);
117         setToolTipText(toolTipText);
118     }
119 
120     @Override
121     public void mouseClicked(final MouseEvent e)
122     {
123         // Ignore
124     }
125 
126     @Override
127     public void mousePressed(final MouseEvent e)
128     {
129         // Ignore
130     }
131 
132     @Override
133     public void mouseReleased(final MouseEvent e)
134     {
135         // Ignore
136     }
137 
138     @Override
139     public void mouseEntered(final MouseEvent e)
140     {
141         ToolTipManager.sharedInstance().setInitialDelay(0);
142     }
143 
144     @Override
145     public void mouseExited(final MouseEvent e)
146     {
147         ToolTipManager.sharedInstance().setInitialDelay(this.defaultInitialDelay);
148     }
149 
150 }
151 
152 /**
153  * Interface for objects that can draw themselves onto a Graphics2D and may want to show their own tool tip text when the mouse
154  * hits them.
155  */
156 interface TrafCODObject
157 {
158     /**
159      * Draw yourself at the indicated location.
160      * @param g2 the graphics context
161      */
162     void draw(Graphics2D g2);
163 
164     /**
165      * Check if the given coordinates hit the TrafCODObject. If it does return a String to be used as a tool tip text. If the
166      * coordinates do not hit this TrafCODObject return null.
167      * @param testX the x-coordinate
168      * @param testY the y-coordinate
169      * @return the tool tip text or empty if the coordinates do not hit the TrafCodObject
170      */
171     Optional<String> toolTipHit(int testX, int testY);
172 
173 }
174 
175 /**
176  * Draws a detector.
177  */
178 class DetectorImage implements TrafCODObject, EventListener
179 {
180     /** The TrafCOD display. */
181     private final TrafCodDisplay display;
182 
183     /** X-coordinate on the TrafCOD display image where this traffic light must be drawn. */
184     private final int x;
185 
186     /** Y-coordinate on the TrafCOD display image where this traffic light must be drawn. */
187     private final int y;
188 
189     /** Tool tip text for this detector image. */
190     private final String description;
191 
192     /** String used to match this detector with the TrafCOD detector input. */
193     private final String id;
194 
195     /** Fill color (used to indicate the occupancy state of the detector). */
196     private Color fillColor = Color.WHITE;
197 
198     /** Size of the box that is drawn. */
199     private static final int BOX_SIZE = 13;
200 
201     /** Correction to make the result match that of the C++Builder version. */
202     private static final int X_OFFSET = 5;
203 
204     /** Correction to make the result match that of the C++Builder version. */
205     private static final int Y_OFFSET = 5;
206 
207     /**
208      * Construct a new DetectorImage.
209      * @param display the TrafCOD display on which this detector image will be rendered
210      * @param center the center location of the detector image on the TrafCOD display
211      * @param id id used to match this detector with the TrafCOD detector input
212      * @param description name of the detector (displayed as tool tip text)
213      */
214     DetectorImage(final TrafCodDisplay display, final Point2D center, final String id, final String description)
215     {
216         this.display = display;
217         this.x = (int) center.getX();
218         this.y = (int) center.getY();
219         this.id = id;
220         this.description = description;
221         display.addTrafCODObject(this);
222     }
223 
224     @Override
225     public void draw(final Graphics2D g2)
226     {
227         g2.setColor(this.fillColor);
228         g2.fillRect(X_OFFSET + this.x - BOX_SIZE / 2, Y_OFFSET + this.y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
229         g2.setColor(Color.BLACK);
230         g2.drawRect(X_OFFSET + this.x - BOX_SIZE / 2, Y_OFFSET + this.y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
231     }
232 
233     @Override
234     public void notify(final Event event)
235     {
236         if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT))
237         {
238             this.fillColor = Color.BLUE;
239         }
240         else if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT))
241         {
242             this.fillColor = Color.WHITE;
243         }
244         this.display.repaint();
245     }
246 
247     @Override
248     public Optional<String> toolTipHit(final int testX, final int testY)
249     {
250         if (testX < X_OFFSET + this.x - BOX_SIZE / 2 || testX >= X_OFFSET + this.x + BOX_SIZE / 2
251                 || testY < Y_OFFSET - BOX_SIZE / 2 + this.y || testY >= Y_OFFSET + this.y + BOX_SIZE / 2)
252         {
253             return Optional.empty();
254         }
255         return Optional.ofNullable(this.description);
256     }
257 
258     /**
259      * Retrieve the id of this DetectorImage.
260      * @return the id of this DetectorImage
261      */
262     public String getId()
263     {
264         return this.id;
265     }
266 
267 }
268 
269 /**
270  * Draws a traffic light. <br>
271  */
272 class TrafficLightImage extends LocalEventProducer implements TrafCODObject
273 {
274     /** The TrafCOD display. */
275     private final TrafCodDisplay display;
276 
277     /** X-coordinate on the TrafCOD display image where this traffic light must be drawn. */
278     private final int x;
279 
280     /** Y-coordinate on the TrafCOD display image where this traffic light must be drawn. */
281     private final int y;
282 
283     /** Tool tip text for this traffic light image. */
284     private final String description;
285 
286     /** The current color. */
287     private TrafficLightColor color = TrafficLightColor.BLACK;
288 
289     /**
290      * Create a traffic light image.
291      * @param display the TrafCOD display on which this traffic light image will be rendered
292      * @param center coordinates in the image where this traffic light is centered on
293      * @param description tool tip text for the new traffic light image
294      */
295     TrafficLightImage(final TrafCodDisplay display, final Point2D center, final String description)
296     {
297         this.display = display;
298         this.x = (int) center.getX();
299         this.y = (int) center.getY();
300         this.description = description;
301         display.addTrafCODObject(this);
302     }
303 
304     @Override
305     public Optional<String> toolTipHit(final int testX, final int testY)
306     {
307         if (testX < this.x - DISC_SIZE / 2 || testX >= this.x + DISC_SIZE / 2 || testY < this.y - DISC_SIZE / 2
308                 || testY >= this.y + DISC_SIZE / 2)
309         {
310             return Optional.empty();
311         }
312         return Optional.ofNullable(this.description);
313     }
314 
315     /**
316      * Sets the traffic light color.
317      * @param trafficLightColor traffic light color.
318      */
319     public void setTrafficLightColor(final TrafficLightColor trafficLightColor)
320     {
321         this.color = trafficLightColor;
322         this.display.repaint();
323     }
324 
325     /** Diameter of a traffic light disk in pixels. */
326     private static final int DISC_SIZE = 11;
327 
328     @Override
329     public void draw(final Graphics2D g2)
330     {
331         Color lightColor;
332         switch (this.color)
333         {
334             case BLACK:
335                 lightColor = Color.BLACK;
336                 break;
337 
338             case GREEN:
339                 lightColor = Color.green;
340                 break;
341 
342             case YELLOW:
343                 lightColor = Color.YELLOW;
344                 break;
345 
346             case RED:
347                 lightColor = Color.RED;
348                 break;
349 
350             default:
351                 Logger.ots().error("Unhandled TrafficLightColor: {}", this.color);
352                 return;
353         }
354         g2.setColor(lightColor);
355         g2.fillOval(this.x - DISC_SIZE / 2, this.y - DISC_SIZE / 2, DISC_SIZE, DISC_SIZE);
356         Logger.ots().trace("Drawn disk in color {}", lightColor);
357     }
358 
359 }