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

Last change on this file since 13661 was 13493, checked in by Don-vip, 6 years ago

see #11924, see #15560, see #16048 - tt HTML tag is deprecated in HTML5: use code instead

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