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
178 @Override
179 public boolean isFont()
180 {
181 return true;
182 }
183
184
185 @Override
186 public String toString()
187 {
188 return "AppearanceControlMenu []";
189 }
190 }
191
192
193 JMenu app = new AppearanceControlMenu("Appearance");
194 app.addMouseListener(new SubMenuShower(app));
195 this.appGroup = new ButtonGroup();
196 for (Appearance appearanceValue : Appearance.values())
197 {
198 this.appGroup.add(addAppearance(app, appearanceValue));
199 }
200 JMenu scale = new AppearanceControlMenu("Font size");
201 scale.addMouseListener(new SubMenuShower(scale));
202 this.scaleGroup = new ButtonGroup();
203 for (String fontScaleName : FONT_SCALES.keySet())
204 {
205 this.scaleGroup.add(addFontsize(scale, fontScaleName));
206 }
207
208
209 class AppearanceControlPopupMenu extends JPopupMenu implements AppearanceControl
210 {
211
212 private static final long serialVersionUID = 20180206L;
213
214
215 @Override
216 public boolean isFont()
217 {
218 return true;
219 }
220
221
222 @Override
223 public String toString()
224 {
225 return "AppearanceControlPopupMenu []";
226 }
227 }
228
229
230 this.popMenu = new AppearanceControlPopupMenu();
231 this.popMenu.add(app);
232 this.popMenu.add(scale);
233 ((JPanel) getContentPane()).setComponentPopupMenu(this.popMenu);
234 }
235
236
237
238
239
240 public void setFontScale(final String fontScaleName)
241 {
242 this.fontScaleName = fontScaleName;
243 setAppearance(getAppearance());
244 }
245
246
247
248
249
250 public void setAppearance(final Appearance appearance)
251 {
252 this.appearance = appearance;
253 setAppearance(this.popMenu, appearance);
254 for (Enumeration<AbstractButton> c = this.appGroup.getElements(); c.hasMoreElements();)
255 {
256 setAppearance(c.nextElement(), appearance);
257 }
258 for (Enumeration<AbstractButton> c = this.scaleGroup.getElements(); c.hasMoreElements();)
259 {
260 setAppearance(c.nextElement(), appearance);
261 }
262 setAppearance(getContentPane(), appearance);
263 this.frameProperties.setProperty("Appearance", appearance.toString());
264 this.frameProperties.setProperty("FontScale", this.fontScaleName);
265 }
266
267
268
269
270
271
272 private void setAppearance(final Component c, final Appearance appear)
273 {
274 if (c instanceof AppearanceControl)
275 {
276 AppearanceControl ac = (AppearanceControl) c;
277 if (ac.isBackground())
278 {
279 c.setBackground(appear.getBackground());
280 }
281 if (ac.isForeground())
282 {
283 c.setForeground(appear.getForeground());
284 }
285 if (ac.isFont())
286 {
287 changeFont(c, appear.getFont());
288 }
289 if (ac.getFontSize() != null)
290 {
291 changeFontSize(c);
292 }
293 }
294 else if (VisualizationPanel.class.isAssignableFrom(c.getClass()))
295 {
296
297 c.setBackground(appear.getBackdrop());
298 c.setForeground(appear.getForeground());
299 changeFont(c, appear.getFont());
300 changeFontSize(c);
301 }
302 else
303 {
304
305 c.setBackground(appear.getBackground());
306 c.setForeground(appear.getForeground());
307 changeFont(c, appear.getFont());
308 changeFontSize(c);
309 }
310 if (c instanceof JSlider)
311 {
312
313 Dictionary<?, ?> dictionary = ((JSlider) c).getLabelTable();
314 Enumeration<?> keys = dictionary.keys();
315 while (keys.hasMoreElements())
316 {
317 JLabel label = (JLabel) dictionary.get(keys.nextElement());
318 label.setForeground(appear.getForeground());
319 label.setBackground(appear.getBackground());
320 }
321 }
322
323 if (c instanceof JComponent)
324 {
325 for (Component child : ((JComponent) c).getComponents())
326 {
327 setAppearance(child, appear);
328 }
329 }
330 }
331
332
333
334
335
336
337 protected void changeFont(final Component c, final String font)
338 {
339 Font prev = c.getFont();
340 c.setFont(new Font(font, prev.getStyle(), prev.getSize()));
341 }
342
343
344
345
346
347 protected void changeFontSize(final Component c)
348 {
349 Font prev = c.getFont();
350 int size;
351 if (c instanceof AppearanceControl)
352 {
353 AppearanceControl a = (AppearanceControl) c;
354 if (a.getFontSize() != null)
355 {
356 size = (int) (a.getFontSize() * FONT_SCALES.get(this.fontScaleName));
357 }
358 else
359 {
360 size = prev.getSize();
361 }
362 }
363 else
364 {
365 size = (int) (AppearanceControl.DEFAULT_FONT_SIZE * FONT_SCALES.get(this.fontScaleName));
366 }
367 c.setFont(new Font(prev.getFontName(), prev.getStyle(), size));
368 }
369
370
371
372
373
374 public Appearance getAppearance()
375 {
376 return this.appearance;
377 }
378
379
380
381
382
383
384
385 private JMenuItem addAppearance(final JMenu group, final Appearance appear)
386 {
387 JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(appear.getName(), appear.equals(getAppearance()));
388 check.addMouseListener(new MouseAdapter()
389 {
390
391 @Override
392 public void mouseClicked(final MouseEvent e)
393 {
394 setAppearance(appear);
395 }
396 });
397 return group.add(check);
398 }
399
400
401
402
403
404
405
406 private JMenuItem addFontsize(final JMenu group, final String fontScaleName)
407 {
408 JCheckBoxMenuItem check = new StayOpenCheckBoxMenuItem(fontScaleName, this.fontScaleName.equals(fontScaleName));
409 check.addMouseListener(new MouseAdapter()
410 {
411
412 @Override
413 public void mouseClicked(final MouseEvent e)
414 {
415 setFontScale(fontScaleName);
416 }
417 });
418 return group.add(check);
419 }
420
421
422
423
424 public static void setDefaultFont()
425 {
426 UIManager.put("Label.font", FONT);
427 UIManager.put("Menu.font", FONT);
428 UIManager.put("MenuItem.font", FONT);
429 UIManager.put("TabbedPane.font", FONT);
430 UIManager.put("Table.font", FONT);
431 UIManager.put("TableHeader.font", FONT);
432 UIManager.put("TextField.font", FONT);
433 UIManager.put("Button.font", FONT);
434 UIManager.put("ComboBox.font", FONT);
435 UIManager.put("CheckBox.font", FONT);
436 UIManager.put("CheckBoxMenuItem.font", FONT);
437
438 }
439
440
441
442
443
444
445
446
447
448
449
450
451 private class SubMenuShower extends MouseAdapter
452 {
453
454 private JMenu menu;
455
456
457
458
459
460 SubMenuShower(final JMenu menu)
461 {
462 this.menu = menu;
463 }
464
465
466 @Override
467 public void mouseEntered(final MouseEvent e)
468 {
469 MenuSelectionManager.defaultManager().setSelectedPath(
470 new MenuElement[] {(MenuElement) this.menu.getParent(), this.menu, this.menu.getPopupMenu()});
471 }
472
473
474 @Override
475 public String toString()
476 {
477 return "SubMenuShower [menu=" + this.menu + "]";
478 }
479 }
480
481
482
483
484
485
486
487
488
489
490
491
492 private static class StayOpenCheckBoxMenuItem extends JCheckBoxMenuItem implements AppearanceControl
493 {
494
495 private static final long serialVersionUID = 20180206L;
496
497
498 private static MenuElement[] PATH;
499 {
500 getModel().addChangeListener(new ChangeListener()
501 {
502
503 @Override
504 public void stateChanged(final ChangeEvent e)
505 {
506 if (getModel().isArmed() && isShowing())
507 {
508 setPath(MenuSelectionManager.defaultManager().getSelectedPath());
509 }
510 }
511 });
512 }
513
514
515
516
517
518 public static void setPath(final MenuElement[] path)
519 {
520 StayOpenCheckBoxMenuItem.PATH = path;
521 }
522
523
524
525
526
527
528 StayOpenCheckBoxMenuItem(final String text, final boolean selected)
529 {
530 super(text, selected);
531 }
532
533
534 @Override
535 public void doClick(final int pressTime)
536 {
537 super.doClick(pressTime);
538 for (MenuElement element : PATH)
539 {
540 if (element instanceof JComponent)
541 {
542 ((JComponent) element).setVisible(true);
543 }
544 }
545 JMenu menu = (JMenu) PATH[PATH.length - 3];
546 MenuSelectionManager.defaultManager()
547 .setSelectedPath(new MenuElement[] {(MenuElement) menu.getParent(), menu, menu.getPopupMenu()});
548 }
549
550
551 @Override
552 public boolean isFont()
553 {
554 return true;
555 }
556
557
558 @Override
559 public String toString()
560 {
561 return "StayOpenCheckBoxMenuItem []";
562 }
563 }
564
565 }