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