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