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://github.com/peter-knoppers">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 the background image. This constructor does <b>not</b> make a deep copy of the image. Modifications of the
50       *            image after calling this constructor might have <i>interesting</i> consequences, but should not result in
51       *            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 id of the DetectorImage
63       * @return 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      @Override
78      protected void paintComponent(final Graphics g)
79      {
80          super.paintComponent(g);
81          g.drawImage(this.image, 0, 0, null);
82          for (TrafCODObject tco : this.trafCODObjects)
83          {
84              tco.draw((Graphics2D) g);
85          }
86      }
87  
88      /**
89       * Add one TrafCODObject to this TrafCODDisplay.
90       * @param trafCODObject the TrafCOD object that must be added
91       */
92      void addTrafCODObject(final TrafCODObject trafCODObject)
93      {
94          this.trafCODObjects.add(trafCODObject);
95      }
96  
97      @Override
98      public void mouseDragged(final MouseEvent e)
99      {
100         mouseMoved(e); // Do the same as in the mouse move event
101     }
102 
103     @Override
104     public void mouseMoved(final MouseEvent e)
105     {
106         String toolTipText = null;
107         for (TrafCODObject tco : this.trafCODObjects)
108         {
109             toolTipText = tco.toolTipHit(e.getX(), e.getY());
110             if (null != toolTipText)
111             {
112                 break;
113             }
114         }
115         // System.out.println("Setting tool tip text to " + toolTipText);
116         setToolTipText(toolTipText);
117     }
118 
119     @Override
120     public void mouseClicked(final MouseEvent e)
121     {
122         // Ignore
123     }
124 
125     @Override
126     public void mousePressed(final MouseEvent e)
127     {
128         // Ignore
129     }
130 
131     @Override
132     public void mouseReleased(final MouseEvent e)
133     {
134         // Ignore
135     }
136 
137     @Override
138     public void mouseEntered(final MouseEvent e)
139     {
140         ToolTipManager.sharedInstance().setInitialDelay(0);
141     }
142 
143     @Override
144     public void mouseExited(final MouseEvent e)
145     {
146         ToolTipManager.sharedInstance().setInitialDelay(this.defaultInitialDelay);
147     }
148 
149 }
150 
151 /**
152  * Interface for objects that can draw themselves onto a Graphics2D and may want to show their own tool tip text when the mouse
153  * hits them.
154  */
155 interface TrafCODObject
156 {
157     /**
158      * Draw yourself at the indicated location.
159      * @param g2 the graphics context
160      */
161     void draw(Graphics2D g2);
162 
163     /**
164      * Check if the given coordinates hit the TrafCODObject. If it does return a String to be used as a tool tip text. If the
165      * coordinates do not hit this TrafCODObject return null.
166      * @param testX the x-coordinate
167      * @param testY the y-coordinate
168      * @return the tool tip text or null if the coordinates do not hit the TrafCodObject
169      */
170     String toolTipHit(int testX, int testY);
171 
172 }
173 
174 /**
175  * Draws a detector.
176  */
177 class DetectorImage implements TrafCODObject, EventListener
178 {
179     /** ... */
180     private static final long serialVersionUID = 20200313L;
181 
182     /** The TrafCOD display. */
183     private final TrafCodDisplay display;
184 
185     /** X-coordinate on the TrafCOD display image where this traffic light must be drawn. */
186     private final int x;
187 
188     /** Y-coordinate on the TrafCOD display image where this traffic light must be drawn. */
189     private final int y;
190 
191     /** Tool tip text for this detector image. */
192     private final String description;
193 
194     /** String used to match this detector with the TrafCOD detector input. */
195     private final String id;
196 
197     /** Fill color (used to indicate the occupancy state of the detector). */
198     private Color fillColor = Color.WHITE;
199 
200     /** Size of the box that is drawn. */
201     private static final int BOX_SIZE = 13;
202 
203     /** Correction to make the result match that of the C++Builder version. */
204     private static final int X_OFFSET = 5;
205 
206     /** Correction to make the result match that of the C++Builder version. */
207     private static final int Y_OFFSET = 5;
208 
209     /**
210      * Construct a new DetectorImage.
211      * @param display the TrafCOD display on which this detector image will be rendered
212      * @param center the center location of the detector image on the TrafCOD display
213      * @param id id used to match this detector with the TrafCOD detector input
214      * @param description name of the detector (displayed as tool tip text)
215      */
216     DetectorImage(final TrafCodDisplay display, final Point2D center, final String id, final String description)
217     {
218         this.display = display;
219         this.x = (int) center.getX();
220         this.y = (int) center.getY();
221         this.id = id;
222         this.description = description;
223         display.addTrafCODObject(this);
224     }
225 
226     @Override
227     public void draw(final Graphics2D g2)
228     {
229         g2.setColor(this.fillColor);
230         g2.fillRect(X_OFFSET + this.x - BOX_SIZE / 2, Y_OFFSET + this.y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
231         g2.setColor(Color.BLACK);
232         g2.drawRect(X_OFFSET + this.x - BOX_SIZE / 2, Y_OFFSET + this.y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
233     }
234 
235     @Override
236     public void notify(final Event event) throws RemoteException
237     {
238         if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT))
239         {
240             this.fillColor = Color.BLUE;
241         }
242         else if (event.getType().equals(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT))
243         {
244             this.fillColor = Color.WHITE;
245         }
246         this.display.repaint();
247     }
248 
249     @Override
250     public String toolTipHit(final int testX, final int testY)
251     {
252         if (testX < X_OFFSET + this.x - BOX_SIZE / 2 || testX >= X_OFFSET + this.x + BOX_SIZE / 2
253                 || testY < Y_OFFSET - BOX_SIZE / 2 + this.y || testY >= Y_OFFSET + this.y + BOX_SIZE / 2)
254         {
255             return null;
256         }
257         return this.description;
258     }
259 
260     /**
261      * Retrieve the id of this DetectorImage.
262      * @return the id of this DetectorImage
263      */
264     public String getId()
265     {
266         return this.id;
267     }
268 
269 }
270 
271 /**
272  * Draws a traffic light. <br>
273  */
274 class TrafficLightImage extends LocalEventProducer implements TrafCODObject
275 {
276     /** ... */
277     private static final long serialVersionUID = 20200313L;
278 
279     /** The TrafCOD display. */
280     private final TrafCodDisplay display;
281 
282     /** X-coordinate on the TrafCOD display image where this traffic light must be drawn. */
283     private final int x;
284 
285     /** Y-coordinate on the TrafCOD display image where this traffic light must be drawn. */
286     private final int y;
287 
288     /** Tool tip text for this traffic light image. */
289     private final String description;
290 
291     /** The current color. */
292     private TrafficLightColor color = TrafficLightColor.BLACK;
293 
294     /**
295      * Create a traffic light image.
296      * @param display the TrafCOD display on which this traffic light image will be rendered
297      * @param center coordinates in the image where this traffic light is centered on
298      * @param description tool tip text for the new traffic light image
299      */
300     TrafficLightImage(final TrafCodDisplay display, final Point2D center, final String description)
301     {
302         this.display = display;
303         this.x = (int) center.getX();
304         this.y = (int) center.getY();
305         this.description = description;
306         display.addTrafCODObject(this);
307     }
308 
309     @Override
310     public String toolTipHit(final int testX, final int testY)
311     {
312         if (testX < this.x - DISC_SIZE / 2 || testX >= this.x + DISC_SIZE / 2 || testY < this.y - DISC_SIZE / 2
313                 || testY >= this.y + DISC_SIZE / 2)
314         {
315             return null;
316         }
317         return this.description;
318     }
319 
320     /**
321      * Sets the traffic light color.
322      * @param trafficLightColor traffic light color.
323      */
324     public void setTrafficLightColor(final TrafficLightColor trafficLightColor)
325     {
326         this.color = trafficLightColor;
327         this.display.repaint();
328     }
329 
330     /** Diameter of a traffic light disk in pixels. */
331     private static final int DISC_SIZE = 11;
332 
333     @Override
334     public void draw(final Graphics2D g2)
335     {
336         Color lightColor;
337         switch (this.color)
338         {
339             case BLACK:
340                 lightColor = Color.BLACK;
341                 break;
342 
343             case GREEN:
344                 lightColor = Color.green;
345                 break;
346 
347             case YELLOW:
348                 lightColor = Color.YELLOW;
349                 break;
350 
351             case RED:
352                 lightColor = Color.RED;
353                 break;
354 
355             default:
356                 System.err.println("Unhandled TrafficLightColor: " + this.color);
357                 return;
358         }
359         g2.setColor(lightColor);
360         g2.fillOval(this.x - DISC_SIZE / 2, this.y - DISC_SIZE / 2, DISC_SIZE, DISC_SIZE);
361         // System.out.println("Drawn disk in color " + lightColor);
362     }
363 
364 }