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.io.Serializable;
13  import java.rmi.RemoteException;
14  import java.util.LinkedHashSet;
15  import java.util.Set;
16  
17  import javax.swing.JPanel;
18  import javax.swing.ToolTipManager;
19  
20  import org.djunits.value.vdouble.scalar.Length;
21  import org.djutils.event.EventInterface;
22  import org.djutils.event.EventListenerInterface;
23  import org.djutils.event.EventTypeInterface;
24  import org.djutils.event.ref.ReferenceType;
25  import org.opentrafficsim.core.geometry.Bounds;
26  import org.opentrafficsim.core.geometry.DirectedPoint;
27  import org.opentrafficsim.core.geometry.OTSLine3D;
28  import org.opentrafficsim.core.network.LongitudinalDirectionality;
29  import org.opentrafficsim.road.network.lane.Lane;
30  import org.opentrafficsim.road.network.lane.object.sensor.NonDirectionalOccupancySensor;
31  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
32  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
33  
34  /**
35   * Display the current state of a TrafCOD machine.
36   * <p>
37   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
38   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
39   * <p>
40   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Nov 15, 2016 <br>
41   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
42   */
43  public class TrafCODDisplay extends JPanel implements MouseMotionListener, MouseListener
44  {
45      /** */
46      private static final long serialVersionUID = 20161115L;
47  
48      /** Background image. */
49      private final BufferedImage image;
50  
51      /** The set of objects drawn on the image. */
52      private Set<TrafCODObject> trafCODObjects = new LinkedHashSet<>();
53  
54      /** Store the tool tip delay so we can restore it when the mouse exits this TrafCODDisplay. */
55      private final int defaultInitialDelay = ToolTipManager.sharedInstance().getInitialDelay();
56  
57      /**
58       * Construct a new TrafCODDisplay.
59       * @param image BufferedImage; the background image. This constructor does <b>not</b> make a deep copy of the image.
60       *            Modifications of the image after calling this constructor might have <i>interesting</i> consequences, but
61       *            should not result in crashes.
62       */
63      public TrafCODDisplay(final BufferedImage image)
64      {
65          this.image = image;
66          super.setPreferredSize(new Dimension(this.image.getWidth(), this.image.getHeight()));
67          addMouseMotionListener(this);
68      }
69  
70      /**
71       * Look up a DetectorImage.
72       * @param id String; id of the DetectorImage
73       * @return DetectorImage; the detector image with matching id or null.
74       */
75      public DetectorImage getDetectorImage(final String id)
76      {
77          for (TrafCODObject tco : this.trafCODObjects)
78          {
79              if (tco instanceof DetectorImage && ((DetectorImage) tco).getId().equals(id))
80              {
81                  return (DetectorImage) tco;
82              }
83          }
84          return null;
85      }
86  
87      /** {@inheritDoc} */
88      @Override
89      protected void paintComponent(final Graphics g)
90      {
91          super.paintComponent(g);
92          g.drawImage(this.image, 0, 0, null);
93          for (TrafCODObject tco : this.trafCODObjects)
94          {
95              tco.draw((Graphics2D) g);
96          }
97      }
98  
99      /**
100      * Add one TrafCODObject to this TrafCODDisplay.
101      * @param trafCODObject TrafCODObject; the TrafCOD object that must be added
102      */
103     void addTrafCODObject(final TrafCODObject trafCODObject)
104     {
105         this.trafCODObjects.add(trafCODObject);
106     }
107 
108     /** {@inheritDoc} */
109     @Override
110     public void mouseDragged(final MouseEvent e)
111     {
112         mouseMoved(e); // Do the same as in the mouse move event
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     public void mouseMoved(final MouseEvent e)
118     {
119         String toolTipText = null;
120         for (TrafCODObject tco : this.trafCODObjects)
121         {
122             toolTipText = tco.toolTipHit(e.getX(), e.getY());
123             if (null != toolTipText)
124             {
125                 break;
126             }
127         }
128         // System.out.println("Setting tool tip text to " + toolTipText);
129         setToolTipText(toolTipText);
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     public void mouseClicked(final MouseEvent e)
135     {
136         // Ignore
137     }
138 
139     /** {@inheritDoc} */
140     @Override
141     public void mousePressed(final MouseEvent e)
142     {
143         // Ignore
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public void mouseReleased(final MouseEvent e)
149     {
150         // Ignore
151     }
152 
153     /** {@inheritDoc} */
154     @Override
155     public void mouseEntered(final MouseEvent e)
156     {
157         ToolTipManager.sharedInstance().setInitialDelay(0);
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public void mouseExited(final MouseEvent e)
163     {
164         ToolTipManager.sharedInstance().setInitialDelay(this.defaultInitialDelay);
165     }
166 
167 }
168 
169 /**
170  * Interface for objects that can draw themselves onto a Graphics2D and may want to show their own tool tip text when the mouse
171  * hits them.
172  */
173 interface TrafCODObject
174 {
175     /**
176      * Draw yourself at the indicated location.
177      * @param g2 Graphics2D; the graphics context
178      */
179     void draw(Graphics2D g2);
180 
181     /**
182      * Check if the given coordinates hit the TrafCODObject. If it does return a String to be used as a tool tip text. If the
183      * coordinates do not hit this TrafCODObject return null.
184      * @param testX int; the x-coordinate
185      * @param testY int; the y-coordinate
186      * @return String; the tool tip text or null if the coordinates do not hit the TrafCodObject
187      */
188     String toolTipHit(int testX, int testY);
189 
190 }
191 
192 /**
193  * Draws a detector.
194  */
195 class DetectorImage implements TrafCODObject, EventListenerInterface
196 {
197     /** ...  */
198     private static final long serialVersionUID = 20200313L;
199 
200     /** The TrafCOD display. */
201     private final TrafCODDisplay display;
202 
203     /** X-coordinate on the TrafCOD display image where this traffic light must be drawn. */
204     private final int x;
205 
206     /** Y-coordinate on the TrafCOD display image where this traffic light must be drawn. */
207     private final int y;
208 
209     /** Tool tip text for this detector image. */
210     private final String description;
211 
212     /** String used to match this detector with the TrafCOD detector input. */
213     private final String id;
214 
215     /** Fill color (used to indicate the occupancy state of the detector). */
216     private Color fillColor = Color.WHITE;
217 
218     /** Size of the box that is drawn. */
219     private static final int BOX_SIZE = 13;
220 
221     /** Correction to make the result match that of the C++Builder version. */
222     private static final int X_OFFSET = 5;
223 
224     /** Correction to make the result match that of the C++Builder version. */
225     private static final int Y_OFFSET = 5;
226 
227     /**
228      * Construct a new DetectorImage.
229      * @param display TrafCODDisplay; the TrafCOD display on which this detector image will be rendered
230      * @param center Point2D; the center location of the detector image on the TrafCOD display
231      * @param id String; id used to match this detector with the TrafCOD detector input
232      * @param description String; name of the detector (displayed as tool tip text)
233      */
234     DetectorImage(final TrafCODDisplay display, final Point2D center, final String id, final String description)
235     {
236         this.display = display;
237         this.x = (int) center.getX();
238         this.y = (int) center.getY();
239         this.id = id;
240         this.description = description;
241         display.addTrafCODObject(this);
242     }
243 
244     /** {@inheritDoc} */
245     @Override
246     public void draw(final Graphics2D g2)
247     {
248         g2.setColor(this.fillColor);
249         g2.fillRect(X_OFFSET + this.x - BOX_SIZE / 2, Y_OFFSET + this.y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
250         g2.setColor(Color.BLACK);
251         g2.drawRect(X_OFFSET + this.x - BOX_SIZE / 2, Y_OFFSET + this.y - BOX_SIZE / 2, BOX_SIZE, BOX_SIZE);
252     }
253 
254     /** {@inheritDoc} */
255     @Override
256     public void notify(final EventInterface event) throws RemoteException
257     {
258         if (event.getType().equals(NonDirectionalOccupancySensor.NON_DIRECTIONAL_OCCUPANCY_SENSOR_TRIGGER_ENTRY_EVENT))
259         {
260             this.fillColor = Color.BLUE;
261         }
262         else if (event.getType().equals(NonDirectionalOccupancySensor.NON_DIRECTIONAL_OCCUPANCY_SENSOR_TRIGGER_EXIT_EVENT))
263         {
264             this.fillColor = Color.WHITE;
265         }
266         this.display.repaint();
267     }
268 
269     /** {@inheritDoc} */
270     @Override
271     public String toolTipHit(final int testX, final int testY)
272     {
273         if (testX < X_OFFSET + this.x - BOX_SIZE / 2 || testX >= X_OFFSET + this.x + BOX_SIZE / 2
274                 || testY < Y_OFFSET - BOX_SIZE / 2 + this.y || testY >= Y_OFFSET + this.y + BOX_SIZE / 2)
275         {
276             return null;
277         }
278         return this.description;
279     }
280 
281     /**
282      * Retrieve the id of this DetectorImage.
283      * @return String; the id of this DetectorImage
284      */
285     public String getId()
286     {
287         return this.id;
288     }
289 
290 }
291 
292 /**
293  * Draws a traffic light. <br>
294  * The implementation of TrafficLight only implements setTrafficLightColor. All other methods are dummies.
295  */
296 class TrafficLightImage implements TrafficLight, TrafCODObject
297 {
298     /** ...  */
299     private static final long serialVersionUID = 20200313L;
300 
301     /** The TrafCOD display. */
302     private final TrafCODDisplay display;
303 
304     /** X-coordinate on the TrafCOD display image where this traffic light must be drawn. */
305     private final int x;
306 
307     /** Y-coordinate on the TrafCOD display image where this traffic light must be drawn. */
308     private final int y;
309 
310     /** Tool tip text for this traffic light image. */
311     private final String description;
312 
313     /** The current color. */
314     private TrafficLightColor color = TrafficLightColor.BLACK;
315 
316     /**
317      * Create a traffic light image.
318      * @param display TrafCODDisplay; the TrafCOD display on which this traffic light image will be rendered
319      * @param center Point2D; coordinates in the image where this traffic light is centered on
320      * @param description String; tool tip text for the new traffic light image
321      */
322     TrafficLightImage(final TrafCODDisplay display, final Point2D center, final String description)
323     {
324         this.display = display;
325         this.x = (int) center.getX();
326         this.y = (int) center.getY();
327         this.description = description;
328         display.addTrafCODObject(this);
329     }
330 
331     /** {@inheritDoc} */
332     @Override
333     public String toolTipHit(final int testX, final int testY)
334     {
335         if (testX < this.x - DISC_SIZE / 2 || testX >= this.x + DISC_SIZE / 2 || testY < this.y - DISC_SIZE / 2
336                 || testY >= this.y + DISC_SIZE / 2)
337         {
338             return null;
339         }
340         return this.description;
341     }
342 
343     /** {@inheritDoc} */
344     @Override
345     public DirectedPoint getLocation()
346     {
347         return null;
348     }
349 
350     /** {@inheritDoc} */
351     @Override
352     public Bounds getBounds()
353     {
354         return null;
355     }
356 
357     /** {@inheritDoc} */
358     @Override
359     public Lane getLane()
360     {
361         return null;
362     }
363 
364     /** {@inheritDoc} */
365     @Override
366     public LongitudinalDirectionality getDirection()
367     {
368         return LongitudinalDirectionality.DIR_NONE;
369     }
370 
371     /** {@inheritDoc} */
372     @Override
373     public Length getLongitudinalPosition()
374     {
375         return null;
376     }
377 
378     /** {@inheritDoc} */
379     @Override
380     public OTSLine3D getGeometry()
381     {
382         return null;
383     }
384 
385     /** {@inheritDoc} */
386     @Override
387     public Length getHeight()
388     {
389         return null;
390     }
391 
392     /** {@inheritDoc} */
393     @Override
394     public String getId()
395     {
396         return null;
397     }
398 
399     /** {@inheritDoc} */
400     @Override
401     public String getFullId()
402     {
403         return null;
404     }
405     /** {@inheritDoc} */
406     @Override
407     public Serializable getSourceId() throws RemoteException
408     {
409         return null;
410     }
411 
412     /** {@inheritDoc} */
413     @Override
414     public boolean addListener(final EventListenerInterface listener, final EventTypeInterface eventType,
415             final ReferenceType referenceType) throws RemoteException
416     {
417         return false;
418     }
419 
420     /** {@inheritDoc} */
421     @Override
422     public boolean addListener(final EventListenerInterface listener, final EventTypeInterface eventType, final int position)
423             throws RemoteException
424     {
425         return false;
426     }
427 
428     /** {@inheritDoc} */
429     @Override
430     public boolean addListener(final EventListenerInterface listener, final EventTypeInterface eventType, final int position,
431             final ReferenceType referenceType) throws RemoteException
432     {
433         return false;
434     }
435 
436 
437     /** {@inheritDoc} */
438     @Override
439     public boolean addListener(final EventListenerInterface listener, final EventTypeInterface eventType) throws RemoteException
440     {
441         return false;
442     }
443 
444     /** {@inheritDoc} */
445     @Override
446     public boolean hasListeners() throws RemoteException
447     {
448         return false;
449     }
450 
451     /** {@inheritDoc} */
452     @Override
453     public int numberOfListeners(final EventTypeInterface eventType) throws RemoteException
454     {
455         return 0;
456     }
457 
458     /** {@inheritDoc} */
459     @Override
460     public Set<EventTypeInterface> getEventTypesWithListeners() throws RemoteException
461     {
462         return null;
463     }
464 
465     /** {@inheritDoc} */
466     @Override
467     public boolean removeListener(final EventListenerInterface listener, final EventTypeInterface eventType)
468             throws RemoteException
469     {
470         return false;
471     }
472 
473     /** {@inheritDoc} */
474     @Override
475     public TrafficLightColor getTrafficLightColor()
476     {
477         return null;
478     }
479 
480     /** {@inheritDoc} */
481     @Override
482     public void setTrafficLightColor(final TrafficLightColor trafficLightColor)
483     {
484         this.color = trafficLightColor;
485         this.display.repaint();
486     }
487 
488     /** Diameter of a traffic light disk in pixels. */
489     private static final int DISC_SIZE = 11;
490 
491     /** {@inheritDoc} */
492     @Override
493     public void draw(final Graphics2D g2)
494     {
495         Color lightColor;
496         switch (this.color)
497         {
498             case BLACK:
499                 lightColor = Color.BLACK;
500                 break;
501 
502             case GREEN:
503                 lightColor = Color.green;
504                 break;
505 
506             case YELLOW:
507                 lightColor = Color.YELLOW;
508                 break;
509 
510             case RED:
511                 lightColor = Color.RED;
512                 break;
513 
514             default:
515                 System.err.println("Unhandled TrafficLightColor: " + this.color);
516                 return;
517         }
518         g2.setColor(lightColor);
519         g2.fillOval(this.x - DISC_SIZE / 2, this.y - DISC_SIZE / 2, DISC_SIZE, DISC_SIZE);
520         // System.out.println("Drawn disk in color " + lightColor);
521     }
522 
523 }