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