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