source: josm/trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java@ 18365

Last change on this file since 18365 was 18365, checked in by Don-vip, 2 years ago

see #15182 - make JOSM callable as standalone validator (patch by taylor.smock)

  • Property svn:eol-style set to native
File size: 25.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.util;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Container;
9import java.awt.Dialog;
10import java.awt.Dimension;
11import java.awt.DisplayMode;
12import java.awt.Font;
13import java.awt.Frame;
14import java.awt.GraphicsDevice;
15import java.awt.GraphicsEnvironment;
16import java.awt.GridBagLayout;
17import java.awt.HeadlessException;
18import java.awt.Image;
19import java.awt.Stroke;
20import java.awt.Toolkit;
21import java.awt.Window;
22import java.awt.event.ActionListener;
23import java.awt.event.MouseAdapter;
24import java.awt.event.MouseEvent;
25import java.awt.image.FilteredImageSource;
26import java.lang.reflect.InvocationTargetException;
27import java.util.Arrays;
28import java.util.Collection;
29import java.util.Enumeration;
30import java.util.EventObject;
31import java.util.Locale;
32import java.util.concurrent.Callable;
33import java.util.concurrent.ExecutionException;
34import java.util.concurrent.FutureTask;
35import java.util.stream.Stream;
36
37import javax.swing.GrayFilter;
38import javax.swing.ImageIcon;
39import javax.swing.JColorChooser;
40import javax.swing.JComponent;
41import javax.swing.JFileChooser;
42import javax.swing.JLabel;
43import javax.swing.JOptionPane;
44import javax.swing.JPanel;
45import javax.swing.JPopupMenu;
46import javax.swing.JScrollPane;
47import javax.swing.Scrollable;
48import javax.swing.SwingUtilities;
49import javax.swing.Timer;
50import javax.swing.ToolTipManager;
51import javax.swing.UIManager;
52import javax.swing.plaf.FontUIResource;
53
54import org.openstreetmap.josm.data.preferences.StrokeProperty;
55import org.openstreetmap.josm.gui.ExtendedDialog;
56import org.openstreetmap.josm.gui.MainApplication;
57import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
58import org.openstreetmap.josm.gui.widgets.HtmlPanel;
59import org.openstreetmap.josm.tools.CheckParameterUtil;
60import org.openstreetmap.josm.tools.ColorHelper;
61import org.openstreetmap.josm.tools.Destroyable;
62import org.openstreetmap.josm.tools.GBC;
63import org.openstreetmap.josm.tools.ImageOverlay;
64import org.openstreetmap.josm.tools.ImageProvider;
65import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
66import org.openstreetmap.josm.tools.LanguageInfo;
67import org.openstreetmap.josm.tools.Logging;
68import org.openstreetmap.josm.tools.bugreport.BugReport;
69import org.openstreetmap.josm.tools.bugreport.ReportedException;
70
71/**
72 * basic gui utils
73 */
74public final class GuiHelper {
75
76 /* Localization keys for file chooser (and color chooser). */
77 private static final String[] JAVA_INTERNAL_MESSAGE_KEYS = {
78 /* JFileChooser windows laf */
79 "FileChooser.detailsViewActionLabelText",
80 "FileChooser.detailsViewButtonAccessibleName",
81 "FileChooser.detailsViewButtonToolTipText",
82 "FileChooser.fileAttrHeaderText",
83 "FileChooser.fileDateHeaderText",
84 "FileChooser.fileNameHeaderText",
85 "FileChooser.fileNameLabelText",
86 "FileChooser.fileSizeHeaderText",
87 "FileChooser.fileTypeHeaderText",
88 "FileChooser.filesOfTypeLabelText",
89 "FileChooser.homeFolderAccessibleName",
90 "FileChooser.homeFolderToolTipText",
91 "FileChooser.listViewActionLabelText",
92 "FileChooser.listViewButtonAccessibleName",
93 "FileChooser.listViewButtonToolTipText",
94 "FileChooser.lookInLabelText",
95 "FileChooser.newFolderAccessibleName",
96 "FileChooser.newFolderActionLabelText",
97 "FileChooser.newFolderToolTipText",
98 "FileChooser.refreshActionLabelText",
99 "FileChooser.saveInLabelText",
100 "FileChooser.upFolderAccessibleName",
101 "FileChooser.upFolderToolTipText",
102 "FileChooser.viewMenuLabelText",
103
104 /* JFileChooser gtk laf */
105 "FileChooser.acceptAllFileFilterText",
106 "FileChooser.cancelButtonText",
107 "FileChooser.cancelButtonToolTipText",
108 "FileChooser.deleteFileButtonText",
109 "FileChooser.filesLabelText",
110 "FileChooser.filterLabelText",
111 "FileChooser.foldersLabelText",
112 "FileChooser.newFolderButtonText",
113 "FileChooser.newFolderDialogText",
114 "FileChooser.openButtonText",
115 "FileChooser.openButtonToolTipText",
116 "FileChooser.openDialogTitleText",
117 "FileChooser.pathLabelText",
118 "FileChooser.renameFileButtonText",
119 "FileChooser.renameFileDialogText",
120 "FileChooser.renameFileErrorText",
121 "FileChooser.renameFileErrorTitle",
122 "FileChooser.saveButtonText",
123 "FileChooser.saveButtonToolTipText",
124 "FileChooser.saveDialogTitleText",
125
126 /* JFileChooser motif laf */
127 //"FileChooser.cancelButtonText",
128 //"FileChooser.cancelButtonToolTipText",
129 "FileChooser.enterFileNameLabelText",
130 //"FileChooser.filesLabelText",
131 //"FileChooser.filterLabelText",
132 //"FileChooser.foldersLabelText",
133 "FileChooser.helpButtonText",
134 "FileChooser.helpButtonToolTipText",
135 //"FileChooser.openButtonText",
136 //"FileChooser.openButtonToolTipText",
137 //"FileChooser.openDialogTitleText",
138 //"FileChooser.pathLabelText",
139 //"FileChooser.saveButtonText",
140 //"FileChooser.saveButtonToolTipText",
141 //"FileChooser.saveDialogTitleText",
142 "FileChooser.updateButtonText",
143 "FileChooser.updateButtonToolTipText",
144
145 /* gtk color chooser */
146 "GTKColorChooserPanel.blueText",
147 "GTKColorChooserPanel.colorNameText",
148 "GTKColorChooserPanel.greenText",
149 "GTKColorChooserPanel.hueText",
150 "GTKColorChooserPanel.nameText",
151 "GTKColorChooserPanel.redText",
152 "GTKColorChooserPanel.saturationText",
153 "GTKColorChooserPanel.valueText",
154
155 /* JOptionPane */
156 "OptionPane.okButtonText",
157 "OptionPane.yesButtonText",
158 "OptionPane.noButtonText",
159 "OptionPane.cancelButtonText"
160 };
161
162 private GuiHelper() {
163 // Hide default constructor for utils classes
164 }
165
166 /**
167 * disable / enable a component and all its child components
168 * @param root component
169 * @param enabled enabled state
170 */
171 public static void setEnabledRec(Container root, boolean enabled) {
172 root.setEnabled(enabled);
173 Component[] children = root.getComponents();
174 for (Component child : children) {
175 if (child instanceof Container) {
176 setEnabledRec((Container) child, enabled);
177 } else {
178 child.setEnabled(enabled);
179 }
180 }
181 }
182
183 /**
184 * Add a task to the main worker that will block the worker and run in the GUI thread.
185 * @param task The task to run
186 */
187 public static void executeByMainWorkerInEDT(final Runnable task) {
188 MainApplication.worker.submit(() -> runInEDTAndWait(task));
189 }
190
191 /**
192 * Executes asynchronously a runnable in
193 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>,
194 * except if we're already in the EDT: in this case the runnable is executed synchronously.
195 * @param task The runnable to execute
196 * @see SwingUtilities#invokeLater
197 */
198 public static void runInEDT(Runnable task) {
199 if (SwingUtilities.isEventDispatchThread()) {
200 task.run();
201 } else {
202 SwingUtilities.invokeLater(task);
203 }
204 }
205
206 /**
207 * Handle exceptions in the EDT. This should only be used in {@link GuiHelper}
208 * and {@code org.openstreetmap.josm.testutils.mockers.EDTAssertionMocker}.
209 *
210 * @param t The throwable to handle
211 */
212 static void handleEDTException(Throwable t) {
213 Logging.logWithStackTrace(Logging.LEVEL_ERROR, t, "Exception raised in EDT");
214 }
215
216 /**
217 * Executes synchronously a runnable in
218 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
219 * @param task The runnable to execute
220 * @see SwingUtilities#invokeAndWait
221 */
222 public static void runInEDTAndWait(Runnable task) {
223 if (SwingUtilities.isEventDispatchThread()) {
224 task.run();
225 } else {
226 try {
227 SwingUtilities.invokeAndWait(task);
228 } catch (InterruptedException | InvocationTargetException e) {
229 handleEDTException(e);
230 }
231 }
232 }
233
234 /**
235 * Executes synchronously a runnable in
236 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
237 * <p>
238 * Passes on the exception that was thrown to the thread calling this.
239 * The exception is wrapped using a {@link ReportedException}.
240 * @param task The runnable to execute
241 * @see SwingUtilities#invokeAndWait
242 * @since 10271
243 */
244 public static void runInEDTAndWaitWithException(Runnable task) {
245 if (SwingUtilities.isEventDispatchThread()) {
246 task.run();
247 } else {
248 try {
249 SwingUtilities.invokeAndWait(task);
250 } catch (InterruptedException | InvocationTargetException e) {
251 throw BugReport.intercept(e).put("task", task);
252 }
253 }
254 }
255
256 /**
257 * Executes synchronously a callable in
258 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>
259 * and return a value.
260 * @param <V> the result type of method <code>call</code>
261 * @param callable The callable to execute
262 * @return The computed result
263 * @since 7204
264 */
265 public static <V> V runInEDTAndWaitAndReturn(Callable<V> callable) {
266 if (SwingUtilities.isEventDispatchThread()) {
267 try {
268 return callable.call();
269 } catch (Exception e) { // NOPMD
270 handleEDTException(e);
271 return null;
272 }
273 } else {
274 FutureTask<V> task = new FutureTask<>(callable);
275 SwingUtilities.invokeLater(task);
276 try {
277 return task.get();
278 } catch (InterruptedException | ExecutionException e) {
279 handleEDTException(e);
280 return null;
281 }
282 }
283 }
284
285 /**
286 * This function fails if it was not called from the EDT thread.
287 * @throws IllegalStateException if called from wrong thread.
288 * @since 10271
289 */
290 public static void assertCallFromEdt() {
291 if (!SwingUtilities.isEventDispatchThread() && !GraphicsEnvironment.isHeadless()) {
292 throw new IllegalStateException(
293 "Needs to be called from the EDT thread, not from " + Thread.currentThread().getName());
294 }
295 }
296
297 /**
298 * Warns user about a dangerous action requiring confirmation.
299 * @param title Title of dialog
300 * @param content Content of dialog
301 * @param baseActionIcon Unused? FIXME why is this parameter unused?
302 * @param continueToolTip Tooltip to display for "continue" button
303 * @return true if the user wants to cancel, false if they want to continue
304 */
305 public static boolean warnUser(String title, String content, ImageIcon baseActionIcon, String continueToolTip) {
306 ExtendedDialog dlg = new ExtendedDialog(MainApplication.getMainFrame(),
307 title, tr("Cancel"), tr("Continue"));
308 dlg.setContent(content);
309 dlg.setButtonIcons(
310 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
311 new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
312 new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get());
313 dlg.setToolTipTexts(tr("Cancel"), continueToolTip);
314 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
315 dlg.setCancelButton(1);
316 return dlg.showDialog().getValue() != 2;
317 }
318
319 /**
320 * Notifies user about an error received from an external source as an HTML page.
321 * @param parent Parent component
322 * @param title Title of dialog
323 * @param message Message displayed at the top of the dialog
324 * @param html HTML content to display (real error message)
325 * @since 7312
326 */
327 public static void notifyUserHtmlError(Component parent, String title, String message, String html) {
328 JPanel p = new JPanel(new GridBagLayout());
329 p.add(new JLabel(message), GBC.eol());
330 p.add(new JLabel(tr("Received error page:")), GBC.eol());
331 JScrollPane sp = embedInVerticalScrollPane(new HtmlPanel(html));
332 sp.setPreferredSize(new Dimension(640, 240));
333 p.add(sp, GBC.eol().fill(GBC.BOTH));
334
335 ExtendedDialog ed = new ExtendedDialog(parent, title, tr("OK"));
336 ed.setButtonIcons("ok");
337 ed.setContent(p);
338 ed.showDialog();
339 }
340
341 /**
342 * Replies the disabled (grayed) version of the specified image.
343 * @param image The image to disable
344 * @return The disabled (grayed) version of the specified image, brightened by 20%.
345 * @since 5484
346 */
347 public static Image getDisabledImage(Image image) {
348 return Toolkit.getDefaultToolkit().createImage(
349 new FilteredImageSource(image.getSource(), new GrayFilter(true, 20)));
350 }
351
352 /**
353 * Replies the disabled (grayed) version of the specified icon.
354 * @param icon The icon to disable
355 * @return The disabled (grayed) version of the specified icon, brightened by 20%.
356 * @since 5484
357 */
358 public static ImageIcon getDisabledIcon(ImageIcon icon) {
359 return new ImageIcon(getDisabledImage(icon.getImage()));
360 }
361
362 /**
363 * Attaches a {@code HierarchyListener} to the specified {@code Component} that
364 * will set its parent dialog resizeable. Use it before a call to JOptionPane#showXXXXDialog
365 * to make it resizeable.
366 * @param pane The component that will be displayed
367 * @param minDimension The minimum dimension that will be set for the dialog. Ignored if null
368 * @return {@code pane}
369 * @since 5493
370 */
371 public static Component prepareResizeableOptionPane(final Component pane, final Dimension minDimension) {
372 if (pane != null) {
373 pane.addHierarchyListener(e -> {
374 Window window = SwingUtilities.getWindowAncestor(pane);
375 if (window instanceof Dialog) {
376 Dialog dialog = (Dialog) window;
377 if (!dialog.isResizable()) {
378 dialog.setResizable(true);
379 if (minDimension != null) {
380 dialog.setMinimumSize(minDimension);
381 }
382 }
383 }
384 });
385 }
386 return pane;
387 }
388
389 /**
390 * Schedules a new Timer to be run in the future (once or several times).
391 * @param initialDelay milliseconds for the initial and between-event delay if repeatable
392 * @param actionListener an initial listener; can be null
393 * @param repeats specify false to make the timer stop after sending its first action event
394 * @return The (started) timer.
395 * @since 5735
396 */
397 public static Timer scheduleTimer(int initialDelay, ActionListener actionListener, boolean repeats) {
398 Timer timer = new Timer(initialDelay, actionListener);
399 timer.setRepeats(repeats);
400 timer.start();
401 return timer;
402 }
403
404 /**
405 * Return s new BasicStroke object with given thickness and style
406 * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
407 * @return stroke for drawing
408 * @see StrokeProperty
409 */
410 public static Stroke getCustomizedStroke(String code) {
411 return StrokeProperty.getFromString(code);
412 }
413
414 /**
415 * Gets the font used to display monospaced text in a component, if possible.
416 * @param component The component
417 * @return the font used to display monospaced text in a component, if possible
418 * @since 7896
419 */
420 public static Font getMonospacedFont(JComponent component) {
421 // Special font for Khmer script
422 if ("km".equals(LanguageInfo.getJOSMLocaleCode())) {
423 return component.getFont();
424 } else {
425 return new Font("Monospaced", component.getFont().getStyle(), component.getFont().getSize());
426 }
427 }
428
429 /**
430 * Gets the font used to display JOSM title in about dialog and splash screen.
431 * @return title font
432 * @since 5797
433 */
434 public static Font getTitleFont() {
435 return new Font("SansSerif", Font.BOLD, 23);
436 }
437
438 /**
439 * Embeds the given component into a new vertical-only scrollable {@code JScrollPane}.
440 * @param panel The component to embed
441 * @return the vertical scrollable {@code JScrollPane}
442 * @since 6666
443 */
444 public static JScrollPane embedInVerticalScrollPane(Component panel) {
445 return new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
446 }
447
448 /**
449 * Set the default unit increment for a {@code JScrollPane}.
450 *
451 * This fixes slow mouse wheel scrolling when the content of the {@code JScrollPane}
452 * is a {@code JPanel} or other component that does not implement the {@link Scrollable}
453 * interface.
454 * The default unit increment is 1 pixel. Multiplied by the number of unit increments
455 * per mouse wheel "click" (platform dependent, usually 3), this makes a very
456 * sluggish mouse wheel experience.
457 * This methods sets the unit increment to a larger, more reasonable value.
458 * @param sp the scroll pane
459 * @return the scroll pane (same object) with fixed unit increment
460 * @throws IllegalArgumentException if the component inside of the scroll pane
461 * implements the {@code Scrollable} interface ({@code JTree}, {@code JLayer},
462 * {@code JList}, {@code JTextComponent} and {@code JTable})
463 */
464 public static JScrollPane setDefaultIncrement(JScrollPane sp) {
465 if (sp.getViewport().getView() instanceof Scrollable) {
466 throw new IllegalArgumentException();
467 }
468 sp.getVerticalScrollBar().setUnitIncrement(10);
469 sp.getHorizontalScrollBar().setUnitIncrement(10);
470 return sp;
471 }
472
473 /**
474 * Sets a global font for all UI, replacing default font of current look and feel.
475 * @param name Font name. It is up to the caller to make sure the font exists
476 * @throws IllegalArgumentException if name is null
477 * @since 7896
478 */
479 public static void setUIFont(String name) {
480 CheckParameterUtil.ensureParameterNotNull(name, "name");
481 Logging.info("Setting "+name+" as the default UI font");
482 Enumeration<?> keys = UIManager.getDefaults().keys();
483 while (keys.hasMoreElements()) {
484 Object key = keys.nextElement();
485 Object value = UIManager.get(key);
486 if (value instanceof FontUIResource) {
487 FontUIResource fui = (FontUIResource) value;
488 UIManager.put(key, new FontUIResource(name, fui.getStyle(), fui.getSize()));
489 }
490 }
491 }
492
493 /**
494 * Sets the background color for this component, and adjust the foreground color so the text remains readable.
495 * @param c component
496 * @param background background color
497 * @since 9223
498 */
499 public static void setBackgroundReadable(JComponent c, Color background) {
500 c.setBackground(background);
501 c.setForeground(ColorHelper.getForegroundColor(background));
502 }
503
504 /**
505 * Gets the size of the screen. On systems with multiple displays, the primary display is used.
506 * This method returns always 800x600 in headless mode (useful for unit tests).
507 * @return the size of this toolkit's screen, in pixels, or 800x600
508 * @see Toolkit#getScreenSize
509 * @since 9576
510 */
511 public static Dimension getScreenSize() {
512 return GraphicsEnvironment.isHeadless() ? new Dimension(800, 600) : Toolkit.getDefaultToolkit().getScreenSize();
513 }
514
515 /**
516 * Gets the size of the screen. On systems with multiple displays,
517 * contrary to {@link #getScreenSize()}, the biggest display is used.
518 * This method returns always 800x600 in headless mode (useful for unit tests).
519 * @return the size of maximum screen, in pixels, or 800x600
520 * @see Toolkit#getScreenSize
521 * @since 10470
522 */
523 public static Dimension getMaximumScreenSize() {
524 if (GraphicsEnvironment.isHeadless()) {
525 return new Dimension(800, 600);
526 }
527
528 int height = 0;
529 int width = 0;
530 for (GraphicsDevice gd: GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
531 DisplayMode dm = gd.getDisplayMode();
532 height = Math.max(height, dm.getHeight());
533 width = Math.max(width, dm.getWidth());
534 }
535 if (height == 0 || width == 0) {
536 return new Dimension(800, 600);
537 }
538 return new Dimension(width, height);
539 }
540
541 /**
542 * Returns the first <code>Window</code> ancestor of event source, or
543 * {@code null} if event source is not a component contained inside a <code>Window</code>.
544 * @param e event object
545 * @return a Window, or {@code null}
546 * @since 9916
547 */
548 public static Window getWindowAncestorFor(EventObject e) {
549 if (e != null) {
550 Object source = e.getSource();
551 if (source instanceof Component) {
552 Window ancestor = SwingUtilities.getWindowAncestor((Component) source);
553 if (ancestor != null) {
554 return ancestor;
555 } else {
556 Container parent = ((Component) source).getParent();
557 if (parent instanceof JPopupMenu) {
558 Component invoker = ((JPopupMenu) parent).getInvoker();
559 return SwingUtilities.getWindowAncestor(invoker);
560 }
561 }
562 }
563 }
564 return null;
565 }
566
567 /**
568 * Extends tooltip dismiss delay to a default value of 1 minute for the given component.
569 * @param c component
570 * @since 10024
571 */
572 public static void extendTooltipDelay(Component c) {
573 extendTooltipDelay(c, 60_000);
574 }
575
576 /**
577 * Extends tooltip dismiss delay to the specified value for the given component.
578 * @param c component
579 * @param delay tooltip dismiss delay in milliseconds
580 * @see <a href="http://stackoverflow.com/a/6517902/2257172">http://stackoverflow.com/a/6517902/2257172</a>
581 * @since 10024
582 */
583 public static void extendTooltipDelay(Component c, final int delay) {
584 final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
585 c.addMouseListener(new MouseAdapter() {
586 @Override
587 public void mouseEntered(MouseEvent me) {
588 ToolTipManager.sharedInstance().setDismissDelay(delay);
589 }
590
591 @Override
592 public void mouseExited(MouseEvent me) {
593 ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout);
594 }
595 });
596 }
597
598 /**
599 * Returns the specified component's <code>Frame</code> without throwing exception in headless mode.
600 *
601 * @param parentComponent the <code>Component</code> to check for a <code>Frame</code>
602 * @return the <code>Frame</code> that contains the component, or <code>getRootFrame</code>
603 * if the component is <code>null</code>, or does not have a valid <code>Frame</code> parent
604 * @see JOptionPane#getFrameForComponent
605 * @see GraphicsEnvironment#isHeadless
606 * @since 10035
607 */
608 public static Frame getFrameForComponent(Component parentComponent) {
609 try {
610 return JOptionPane.getFrameForComponent(parentComponent);
611 } catch (HeadlessException e) {
612 Logging.debug(e);
613 return null;
614 }
615 }
616
617 /**
618 * Localizations for file chooser dialog.
619 * For some locales (e.g. de, fr) translations are provided
620 * by Java, but not for others (e.g. ru, uk).
621 * @since 12644 (moved from I18n)
622 */
623 public static void translateJavaInternalMessages() {
624 Locale l = Locale.getDefault();
625
626 AbstractFileChooser.setDefaultLocale(l);
627 JFileChooser.setDefaultLocale(l);
628 JColorChooser.setDefaultLocale(l);
629 for (String key : JAVA_INTERNAL_MESSAGE_KEYS) {
630 String us = UIManager.getString(key, Locale.US);
631 String loc = UIManager.getString(key, l);
632 // only provide custom translation if it is not already localized by Java
633 if (us != null && us.equals(loc)) {
634 UIManager.put(key, tr(us));
635 }
636 }
637 }
638
639 /**
640 * Setup special font for Khmer script, as the default Java fonts do not display these characters.
641 * @since 12644 (moved from I18n)
642 * @since 8282
643 */
644 public static void setupLanguageFonts() {
645 // Use special font for Khmer script, as the default Java font do not display these characters
646 if ("km".equals(LanguageInfo.getJOSMLocaleCode())) {
647 Collection<String> fonts = Arrays.asList(
648 GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
649 Stream.of("Khmer UI", "DaunPenh", "MoolBoran")
650 .filter(fonts::contains)
651 .findFirst()
652 .ifPresent(GuiHelper::setUIFont);
653 }
654 }
655
656 /**
657 * Destroys recursively all {@link Destroyable} components of a given container, and optionnally the container itself.
658 * @param component the component to destroy
659 * @param destroyItself whether to destroy the component itself
660 * @since 14463
661 */
662 public static void destroyComponents(Component component, boolean destroyItself) {
663 if (component instanceof Container) {
664 for (Component c: ((Container) component).getComponents()) {
665 destroyComponents(c, true);
666 }
667 }
668 if (destroyItself && component instanceof Destroyable) {
669 ((Destroyable) component).destroy();
670 }
671 }
672}
Note: See TracBrowser for help on using the repository browser.