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