1 package org.opentrafficsim.swing.gui;
2
3 import java.awt.Component;
4 import java.awt.Font;
5 import java.awt.event.MouseAdapter;
6 import java.awt.event.MouseEvent;
7 import java.awt.event.WindowAdapter;
8 import java.awt.event.WindowEvent;
9 import java.io.File;
10 import java.io.FileReader;
11 import java.io.FileWriter;
12 import java.io.IOException;
13 import java.util.Dictionary;
14 import java.util.Enumeration;
15 import java.util.LinkedHashMap;
16 import java.util.Map;
17 import java.util.Properties;
18
19 import javax.imageio.ImageIO;
20 import javax.swing.AbstractButton;
21 import javax.swing.ButtonGroup;
22 import javax.swing.JCheckBoxMenuItem;
23 import javax.swing.JComponent;
24 import javax.swing.JFrame;
25 import javax.swing.JLabel;
26 import javax.swing.JMenu;
27 import javax.swing.JMenuItem;
28 import javax.swing.JPanel;
29 import javax.swing.JPopupMenu;
30 import javax.swing.JSlider;
31 import javax.swing.MenuElement;
32 import javax.swing.MenuSelectionManager;
33 import javax.swing.UIManager;
34 import javax.swing.event.ChangeEvent;
35 import javax.swing.event.ChangeListener;
36
37 import org.opentrafficsim.base.Resource;
38
39 import nl.tudelft.simulation.dsol.swing.animation.d2.VisualizationPanel;
40
41
42
43
44
45
46
47
48
49
50
51 public class AppearanceApplication extends JFrame
52 {
53
54
55 private static final long serialVersionUID = 20231017L;
56
57
58 private static final Font FONT = new Font("Dialog", Font.PLAIN, AppearanceControl.DEFAULT_FONT_SIZE);
59
60
61 private static final Map<String, Double> FONT_SCALES = new LinkedHashMap<>();
62
63 static
64 {
65 FONT_SCALES.put("Small", 10.0 / 12.0);
66 FONT_SCALES.put("Normal", 1.0);
67 FONT_SCALES.put("Large", 14.0 / 12.0);
68 FONT_SCALES.put("Very large", 16.0 / 12.0);
69 }
70
71
72 protected Properties frameProperties;
73
74
75 private final JPopupMenu popMenu;
76
77
78 private final ButtonGroup appGroup;
79
80
81 private final ButtonGroup scaleGroup;
82
83
84 private Appearance appearance = Appearance.GRAY;
85
86
87 private String fontScaleName = "Normal";
88
89
90
91
92 public AppearanceApplication()
93 {
94 this(null);
95 }
96
97
98
99
100
101 public AppearanceApplication(final JPanel panel)
102 {
103
104
105
106
107 AppearanceApplication.setDefaultFont();
108
109 if (panel != null)
110 {
111 setContentPane(panel);
112 }
113 try
114 {
115 setIconImage(ImageIO.read(Resource.getResourceAsStream("/OTS_merge.png")));
116 }
117 catch (IOException io)
118 {
119
120 }
121
122
123 String sep = System.getProperty("file.separator");
124 String propertiesFile = System.getProperty("user.home") + sep + "OTS" + sep + "properties.ini";
125 addWindowListener(new WindowAdapter()
126 {
127
128 @Override
129 public void windowClosing(final WindowEvent windowEvent)
130 {
131 try
132 {
133 File f = new File(propertiesFile);
134 f.getParentFile().mkdirs();
135 FileWriter writer = new FileWriter(f);
136 AppearanceApplication.this.frameProperties.store(writer, "OTS user settings");
137 }
138 catch (IOException exception)
139 {
140 System.err.println("Could not store properties at " + propertiesFile + ".");
141 }
142 }
143 });
144
145
146 Properties defaults = new Properties();
147 defaults.setProperty("Appearance", "GRAY");
148 defaults.setProperty("FontScale", "Normal");
149 this.frameProperties = new Properties(defaults);
150 try
151 {
152 FileReader reader = new FileReader(propertiesFile);
153 this.frameProperties.load(reader);
154 }
155 catch (IOException ioe)
156 {
157
158 }
159 this.appearance = Appearance.valueOf(this.frameProperties.getProperty("Appearance").toUpperCase());
160 this.fontScaleName = this.frameProperties.getProperty("FontScale");
161
162
163 class AppearanceControlMenu extends JMenu implements AppearanceControl
164 {
165
166 private static final long serialVersionUID = 20180206L;
167
168
169
170
171
172 AppearanceControlMenu(final String string)
173 {
174 super(string);
175 }
176
177 @Override
178 public boolean isFont()
179 {
180 return true;
181 }
182
183 @Override
184 public String toString()
185 {
186 return "AppearanceControlMenu []";
187 }
188 }
189
190
191 JMenu app = new AppearanceControlMenu("Appearance");
192 app.addMouseListener(new SubMenuShower(app));
193 this.appGroup = new ButtonGroup();
194 for (Appearance appearanceValue : Appearance.values())
195 {
196 this.appGroup.add(addAppearance(app, appearanceValue));
197 }
198 JMenu scale = new AppearanceControlMenu("Font size");
199 scale.addMouseListener(new SubMenuShower(scale));
200 this.scaleGroup = new ButtonGroup();
201 for (String fontScaleName : FONT_SCALES.keySet())
202 {
203 this.scaleGroup.add(addFontsize(scale, fontScaleName));
204 }
205
206
207 class AppearanceControlPopupMenu extends JPopupMenu implements AppearanceControl
208 {
209
210 private static final long serialVersionUID = 20180206L;
211
212 @Override
213 public boolean isFont()
214 {
215 return true;
216 }
217
218 @Override
219 public String toString()
220 {
221 return "AppearanceControlPopupMenu []";
222 }
223 }
224
225
226 this.popMenu = new AppearanceControlPopupMenu();
227 this.popMenu.add(app);
228 this.popMenu.add(scale);
229 ((JPanel) getContentPane()).setComponentPopupMenu(this.popMenu);
230 }
231
232
233
234
235
236 public void setFontScale(final String fontScaleName)
237 {
238 this.fontScaleName = fontScaleName;
239 setAppearance(getAppearance());
240 }
241
242
243
244
245
246 public void setAppearance(final Appearance appearance)
247 {
248 this.appearance = appearance;
249 setAppearance(this.popMenu, appearance);
250 for (Enumeration<AbstractButton> c = this.appGroup.getElements(); c.hasMoreElements();)
251 {
252 setAppearance(c.nextElement(), appearance);
253 }
254 for (Enumeration<AbstractButton> c = this.scaleGroup.getElements(); c.hasMoreElements();)
255 {
256 setAppearance(c.nextElement(), appearance);
257 }
258 setAppearance(getContentPane(), appearance);
259 this.frameProperties.setProperty("Appearance", appearance.toString());
260 this.frameProperties.setProperty("FontScale", this.fontScaleName);
261 }
262
263
264
265
266
267
268 private void setAppearance(final Component c, final Appearance appear)
269 {
270 if (c instanceof AppearanceControl)
271 {
272 AppearanceControl ac = (AppearanceControl) c;
273 if (ac.isBackground())
274 {
275 c.setBackground(appear.getBackground());
276 }
277 if (ac.isForeground())
278 {
279 c.setForeground(appear.getForeground());
280 }
281 if (ac.isFont())
282 {
283 changeFont(c, appear.getFont());
284 }
285 if (ac.getFontSize() != null)
286 {
287 changeFontSize(c);
288 }
289 }
290 else if (VisualizationPanel.class.isAssignableFrom(c.getClass()))
291 {
292
293 c.setBackground(appear.getBackdrop());
294 c.setForeground(appear.getForeground());
295 changeFont(c, appear.getFont());
296 changeFontSize(c);
297 }
298 else
299 {
300
301 c.setBackground(appear.getBackground());
302 c.setForeground(appear.getForeground());
303 changeFont(c, appear.getFont());
304 changeFontSize(c);
305 }
306 if (c instanceof JSlider)
307 {
308
309 Dictionary<?, ?> dictionary = ((JSlider) c).getLabelTable();
310 Enumeration<?> keys = dictionary.keys();
311 while (keys.hasMoreElements())
312 {
313 JLabel label = (JLabel) dictionary.get(keys.nextElement());
314 label.setForeground(appear.getForeground());
315 label.setBackground(appear.getBackground());
316 }
317 }
318
319 if (c instanceof JComponent)
320 {
321 for (Component child : ((JComponent) c).getComponents())
322 {
323 setAppearance(child, appear);
324 }
325 }
326 }
327
328
329
330
331
332
333 protected void changeFont(final Component c, final String font)
334 {
335 Font prev = c.getFont();
336 c.setFont(new Font(font, prev.getStyle(), prev.getSize()));
337 }
338
339
340
341
342
343 protected void changeFontSize(final Component c)
344 {
345 Font prev = c.getFont();
346 int size;
347 if (c instanceof AppearanceControl)
348 {
349 AppearanceControl a = (AppearanceControl) c;
350 if (a.getFontSize() != null)
351 {
352 size = (int) (a.getFontSize() * FONT_SCALES.get(this.fontScaleName));
353 }
354 else
355 {
356 size = prev.getSize();
357 }
358 }
359 else
360 {
361 size = (int) (AppearanceControl.DEFAULT_FONT_SIZE * FONT_SCALES.get(this.fontScaleName));
362 }
363 c.setFont(new Font(prev.getFontName(), prev.getStyle(), size));
364 }
365
366
367
368
369
370 public Appearance getAppearance()
371 {
372 return this.appearance;
373 }
374
375
376
377
378
379
380
381 private JMenuItem addAppearance(final JMenu group, final Appearance appear)
382 {
383 JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(appear.getName(), appear.equals(getAppearance()));
384 check.addMouseListener(new MouseAdapter()
385 {
386 @Override
387 public void mouseClicked(final MouseEvent e)
388 {
389 setAppearance(appear);
390 }
391 });
392 return group.add(check);
393 }
394
395
396
397
398
399
400
401 private JMenuItem addFontsize(final JMenu group, final String fontScaleName)
402 {
403 JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(fontScaleName, this.fontScaleName.equals(fontScaleName));
404 check.addMouseListener(new MouseAdapter()
405 {
406 @Override
407 public void mouseClicked(final MouseEvent e)
408 {
409 setFontScale(fontScaleName);
410 }
411 });
412 return group.add(check);
413 }
414
415
416
417
418 public static void setDefaultFont()
419 {
420 UIManager.put("Label.font", FONT);
421 UIManager.put("Menu.font", FONT);
422 UIManager.put("MenuItem.font", FONT);
423 UIManager.put("TabbedPane.font", FONT);
424 UIManager.put("Table.font", FONT);
425 UIManager.put("TableHeader.font", FONT);
426 UIManager.put("TextField.font", FONT);
427 UIManager.put("Button.font", FONT);
428 UIManager.put("ComboBox.font", FONT);
429 UIManager.put("CheckBox.font", FONT);
430 UIManager.put("CheckBoxMenuItem.font", FONT);
431
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445 private class SubMenuShower extends MouseAdapter
446 {
447
448 private JMenu menu;
449
450
451
452
453
454 SubMenuShower(final JMenu menu)
455 {
456 this.menu = menu;
457 }
458
459 @Override
460 public void mouseEntered(final MouseEvent e)
461 {
462 MenuSelectionManager.defaultManager().setSelectedPath(
463 new MenuElement[] {(MenuElement) this.menu.getParent(), this.menu, this.menu.getPopupMenu()});
464 }
465
466 @Override
467 public String toString()
468 {
469 return "SubMenuShower [menu=" + this.menu + "]";
470 }
471 }
472
473
474
475
476
477
478
479
480
481
482
483
484 private static class StayOpenCheckBoxMenuItem extends JCheckBoxMenuItem implements AppearanceControl
485 {
486
487 private static final long serialVersionUID = 20180206L;
488
489
490 private static MenuElement[] PATH;
491 {
492 getModel().addChangeListener(new ChangeListener()
493 {
494 @Override
495 public void stateChanged(final ChangeEvent e)
496 {
497 if (getModel().isArmed() && isShowing())
498 {
499 setPath(MenuSelectionManager.defaultManager().getSelectedPath());
500 }
501 }
502 });
503 }
504
505
506
507
508
509 public static void setPath(final MenuElement[] path)
510 {
511 StayOpenCheckBoxMenuItem.PATH = path;
512 }
513
514
515
516
517
518
519 StayOpenCheckBoxMenuItem(final String text, final boolean selected)
520 {
521 super(text, selected);
522 }
523
524 @Override
525 public void doClick(final int pressTime)
526 {
527 super.doClick(pressTime);
528 for (MenuElement element : PATH)
529 {
530 if (element instanceof JComponent)
531 {
532 ((JComponent) element).setVisible(true);
533 }
534 }
535 JMenu menu = (JMenu) PATH[PATH.length - 3];
536 MenuSelectionManager.defaultManager()
537 .setSelectedPath(new MenuElement[] {(MenuElement) menu.getParent(), menu, menu.getPopupMenu()});
538 }
539
540 @Override
541 public boolean isFont()
542 {
543 return true;
544 }
545
546 @Override
547 public String toString()
548 {
549 return "StayOpenCheckBoxMenuItem []";
550 }
551 }
552
553 }