View Javadoc
1   package org.opentrafficsim.swing.gui;
2   
3   import java.awt.Component;
4   import java.awt.Font;
5   import java.awt.Frame;
6   import java.awt.event.MouseAdapter;
7   import java.awt.event.MouseEvent;
8   import java.awt.event.WindowAdapter;
9   import java.awt.event.WindowEvent;
10  import java.awt.geom.Rectangle2D;
11  import java.io.File;
12  import java.io.FileReader;
13  import java.io.FileWriter;
14  import java.io.IOException;
15  import java.util.Dictionary;
16  import java.util.Enumeration;
17  import java.util.Properties;
18  
19  import javax.swing.ButtonGroup;
20  import javax.swing.JCheckBoxMenuItem;
21  import javax.swing.JComponent;
22  import javax.swing.JFrame;
23  import javax.swing.JLabel;
24  import javax.swing.JMenu;
25  import javax.swing.JMenuItem;
26  import javax.swing.JPanel;
27  import javax.swing.JPopupMenu;
28  import javax.swing.JSlider;
29  import javax.swing.MenuElement;
30  import javax.swing.MenuSelectionManager;
31  import javax.swing.WindowConstants;
32  import javax.swing.event.ChangeEvent;
33  import javax.swing.event.ChangeListener;
34  
35  import org.opentrafficsim.core.animation.gtu.colorer.DefaultSwitchableGTUColorer;
36  import org.opentrafficsim.core.animation.gtu.colorer.GTUColorer;
37  import org.opentrafficsim.core.dsol.OTSModelInterface;
38  
39  import nl.tudelft.simulation.dsol.swing.animation.D2.AnimationPanel;
40  
41  /**
42   * Wrap a DSOL simulation model, or any (descendant of a) JPanel in a JFrame (wrap it in a window). The window will be
43   * maximized.
44   * <p>
45   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
46   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
47   * <p>
48   * $LastChangedDate: 2018-09-19 13:55:45 +0200 (Wed, 19 Sep 2018) $, @version $Revision: 4006 $, by $Author: averbraeck $,
49   * initial version 16 dec. 2014 <br>
50   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
51   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
52   * @param <T> model type
53   */
54  public class OTSSwingApplication<T extends OTSModelInterface> extends JFrame
55  {
56      /** */
57      private static final long serialVersionUID = 20141216L;
58  
59      /** Single instance of default colorer, reachable from various places. */
60      public static final GTUColorer DEFAULT_COLORER = new DefaultSwitchableGTUColorer();
61  
62      /** the model. */
63      private final T model;
64  
65      /** whether the application has been closed or not. */
66      @SuppressWarnings("checkstyle:visibilitymodifier")
67      protected boolean closed = false;
68  
69      /** Properties for the frame appearance (not simulation related). */
70      protected Properties frameProperties;
71  
72      /** Current appearance. */
73      private Appearance appearance = Appearance.GRAY;
74  
75      /**
76       * Wrap an OTSModel in a JFrame. Uses a default GTU colorer.
77       * @param model T; the model that will be shown in the JFrame
78       * @param panel JPanel; this should be the JPanel of the simulation
79       */
80      public OTSSwingApplication(final T model, final JPanel panel)
81      {
82          this.model = model;
83          setTitle("OTS | The Open Traffic Simulator | " + model.getDescription());
84          setContentPane(panel);
85          pack();
86          setExtendedState(Frame.MAXIMIZED_BOTH);
87          setVisible(true);
88  
89          setExitOnClose(true);
90          addWindowListener(new WindowAdapter()
91          {
92              @Override
93              public void windowClosing(final WindowEvent windowEvent)
94              {
95                  OTSSwingApplication.this.closed = true;
96                  super.windowClosing(windowEvent);
97              }
98          });
99  
100         //////////////////////
101         ///// Appearance /////
102         //////////////////////
103 
104         // Listener to write frame properties on frame close
105         String sep = System.getProperty("file.separator");
106         String propertiesFile = System.getProperty("user.home") + sep + "OTS" + sep + "properties.ini";
107         addWindowListener(new WindowAdapter()
108         {
109             /** {@inheritDoce} */
110             @Override
111             public void windowClosing(final WindowEvent windowEvent)
112             {
113                 try
114                 {
115                     File f = new File(propertiesFile);
116                     f.getParentFile().mkdirs();
117                     FileWriter writer = new FileWriter(f);
118                     OTSSwingApplication.this.frameProperties.store(writer, "OTS user settings");
119                 }
120                 catch (IOException exception)
121                 {
122                     System.err.println("Could not store properties at " + propertiesFile + ".");
123                 }
124             }
125         });
126 
127         // Set default frame properties and load properties from file (if any)
128         Properties defaults = new Properties();
129         defaults.setProperty("Appearance", "GRAY");
130         this.frameProperties = new Properties(defaults);
131         try
132         {
133             FileReader reader = new FileReader(propertiesFile);
134             this.frameProperties.load(reader);
135         }
136         catch (IOException ioe)
137         {
138             // ok, use defaults
139         }
140         this.appearance = Appearance.valueOf(this.frameProperties.getProperty("Appearance").toUpperCase());
141 
142         /** Menu class to only accept the font of an Appearance */
143         class AppearanceControlMenu extends JMenu implements AppearanceControl
144         {
145             /** */
146             private static final long serialVersionUID = 20180206L;
147 
148             /**
149              * Constructor.
150              * @param string String; string
151              */
152             AppearanceControlMenu(final String string)
153             {
154                 super(string);
155             }
156 
157             /** {@inheritDoc} */
158             @Override
159             public boolean isFont()
160             {
161                 return true;
162             }
163 
164             /** {@inheritDoc} */
165             @Override
166             public String toString()
167             {
168                 return "AppearanceControlMenu []";
169             }
170         }
171 
172         // Appearance menu
173         JMenu app = new AppearanceControlMenu("Appearance");
174         app.addMouseListener(new SubMenuShower(app));
175         ButtonGroup appGroup = new ButtonGroup();
176         for (Appearance appearanceValue : Appearance.values())
177         {
178             appGroup.add(addAppearance(app, appearanceValue));
179         }
180 
181         /** PopupMenu class to only accept the font of an Appearance */
182         class AppearanceControlPopupMenu extends JPopupMenu implements AppearanceControl
183         {
184             /** */
185             private static final long serialVersionUID = 20180206L;
186 
187             /** {@inheritDoc} */
188             @Override
189             public boolean isFont()
190             {
191                 return true;
192             }
193 
194             /** {@inheritDoc} */
195             @Override
196             public String toString()
197             {
198                 return "AppearanceControlPopupMenu []";
199             }
200         }
201 
202         // Popup menu to change appearance
203         JPopupMenu popMenu = new AppearanceControlPopupMenu();
204         popMenu.add(app);
205         panel.setComponentPopupMenu(popMenu);
206 
207         // Set the Appearance as by frame properties
208         setAppearance(getAppearance()); // color elements that were just added
209     }
210 
211     /**
212      * Sets an appearance.
213      * @param appearance Appearance; appearance
214      */
215     public void setAppearance(final Appearance appearance)
216     {
217         this.appearance = appearance;
218         setAppearance(this.getContentPane(), appearance);
219         this.frameProperties.setProperty("Appearance", appearance.toString());
220     }
221 
222     /**
223      * Sets an appearance recursively on components.
224      * @param c Component; visual component
225      * @param appear Appearance; look and feel
226      */
227     private void setAppearance(final Component c, final Appearance appear)
228     {
229         if (c instanceof AppearanceControl)
230         {
231             AppearanceControl./../org/opentrafficsim/swing/gui/AppearanceControl.html#AppearanceControl">AppearanceControl ac = (AppearanceControl) c;
232             if (ac.isBackground())
233             {
234                 c.setBackground(appear.getBackground());
235             }
236             if (ac.isForeground())
237             {
238                 c.setForeground(appear.getForeground());
239             }
240             if (ac.isFont())
241             {
242                 changeFont(c, appear.getFont());
243             }
244         }
245         else if (c instanceof AnimationPanel)
246         {
247             // animation backdrop
248             c.setBackground(appear.getBackdrop()); // not background
249             c.setForeground(appear.getForeground());
250             changeFont(c, appear.getFont());
251         }
252         else
253         {
254             // default
255             c.setBackground(appear.getBackground());
256             c.setForeground(appear.getForeground());
257             changeFont(c, appear.getFont());
258         }
259         if (c instanceof JSlider)
260         {
261             // labels of the slider
262             Dictionary<?, ?> dictionary = ((JSlider) c).getLabelTable();
263             Enumeration<?> keys = dictionary.keys();
264             while (keys.hasMoreElements())
265             {
266                 JLabel label = (JLabel) dictionary.get(keys.nextElement());
267                 label.setForeground(appear.getForeground());
268                 label.setBackground(appear.getBackground());
269             }
270         }
271         // children
272         if (c instanceof JComponent)
273         {
274             for (Component child : ((JComponent) c).getComponents())
275             {
276                 setAppearance(child, appear);
277             }
278         }
279     }
280 
281     /**
282      * Change font on component.
283      * @param c Component; component
284      * @param font String; font name
285      */
286     private void changeFont(final Component c, final String font)
287     {
288         Font prev = c.getFont();
289         c.setFont(new Font(font, prev.getStyle(), prev.getSize()));
290     }
291 
292     /**
293      * Returns the appearance.
294      * @return Appearance; appearance
295      */
296     public Appearance getAppearance()
297     {
298         return this.appearance;
299     }
300 
301     /**
302      * Adds an appearance to the menu.
303      * @param group JMenu; menu to add item to
304      * @param appear Appearance; appearance this item selects
305      * @return JMenuItem; menu item
306      */
307     private JMenuItem addAppearance(final JMenu group, final Appearance appear)
308     {
309         JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(appear.getName(), appear.equals(getAppearance()));
310         check.addMouseListener(new MouseAdapter()
311         {
312             /** {@inheritDoc} */
313             @Override
314             public void mouseClicked(final MouseEvent e)
315             {
316                 setAppearance(appear);
317             }
318         });
319         return group.add(check);
320     }
321 
322     /**
323      * Return the initial 'home' extent for the animation. The 'Home' button returns to this extent. Override this method when a
324      * smaller or larger part of the infra should be shown. In the default setting, all currently visible objects are shown.
325      * @return the initial and 'home' rectangle for the animation.
326      */
327     @SuppressWarnings("checkstyle:designforextension")
328     protected Rectangle2D makeAnimationRectangle()
329     {
330         return this.model.getNetwork().getExtent();
331     }
332 
333     /**
334      * @param exitOnClose boolean; set exitOnClose
335      */
336     public final void setExitOnClose(final boolean exitOnClose)
337     {
338         if (exitOnClose)
339         {
340             setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
341         }
342         else
343         {
344             setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
345         }
346     }
347 
348     /**
349      * @return closed
350      */
351     public final boolean isClosed()
352     {
353         return this.closed;
354     }
355 
356     /**
357      * @return model
358      */
359     public final T getModel()
360     {
361         return this.model;
362     }
363 
364     /**
365      * Mouse listener which shows the submenu when the mouse enters the button.
366      * <p>
367      * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
368      * <br>
369      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
370      * <p>
371      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 6 feb. 2018 <br>
372      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
373      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
374      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
375      */
376     private class SubMenuShower extends MouseAdapter
377     {
378         /** The menu. */
379         private JMenu menu;
380 
381         /**
382          * Constructor.
383          * @param menu JMenu; menu
384          */
385         SubMenuShower(final JMenu menu)
386         {
387             this.menu = menu;
388         }
389 
390         /** {@inheritDoc} */
391         @Override
392         public void mouseEntered(final MouseEvent e)
393         {
394             MenuSelectionManager.defaultManager().setSelectedPath(
395                     new MenuElement[] {(MenuElement) this.menu.getParent(), this.menu, this.menu.getPopupMenu()});
396         }
397 
398         /** {@inheritDoc} */
399         @Override
400         public String toString()
401         {
402             return "SubMenuShower [menu=" + this.menu + "]";
403         }
404     }
405 
406     /**
407      * Check box item that keeps the popup menu visible after clicking, so the user can click and try some options.
408      * <p>
409      * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
410      * <br>
411      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
412      * <p>
413      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 6 feb. 2018 <br>
414      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
415      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
416      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
417      */
418     private static class StayOpenCheckBoxMenuItem extends JCheckBoxMenuItem implements AppearanceControl
419     {
420         /** */
421         private static final long serialVersionUID = 20180206L;
422 
423         /** Stored selection path. */
424         private static MenuElement[] path;
425 
426         {
427             getModel().addChangeListener(new ChangeListener()
428             {
429 
430                 @Override
431                 public void stateChanged(final ChangeEvent e)
432                 {
433                     if (getModel().isArmed() && isShowing())
434                     {
435                         setPath(MenuSelectionManager.defaultManager().getSelectedPath());
436                     }
437                 }
438             });
439         }
440 
441         /**
442          * Sets the path.
443          * @param path MenuElement[]; path
444          */
445         public static void setPath(final MenuElement[] path)
446         {
447             StayOpenCheckBoxMenuItem.path = path;
448         }
449 
450         /**
451          * Constructor.
452          * @param text String; menu item text
453          * @param selected boolean; if the item is selected
454          */
455         StayOpenCheckBoxMenuItem(final String text, final boolean selected)
456         {
457             super(text, selected);
458         }
459 
460         /** {@inheritDoc} */
461         @Override
462         public void doClick(final int pressTime)
463         {
464             super.doClick(pressTime);
465             for (MenuElement element : path)
466             {
467                 if (element instanceof JComponent)
468                 {
469                     ((JComponent) element).setVisible(true);
470                 }
471             }
472             JMenu menu = (JMenu) path[path.length - 3];
473             MenuSelectionManager.defaultManager()
474                     .setSelectedPath(new MenuElement[] {(MenuElement) menu.getParent(), menu, menu.getPopupMenu()});
475         }
476 
477         /** {@inheritDoc} */
478         @Override
479         public boolean isFont()
480         {
481             return true;
482         }
483 
484         /** {@inheritDoc} */
485         @Override
486         public String toString()
487         {
488             return "StayOpenCheckBoxMenuItem []";
489         }
490     }
491 
492 }