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

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

fix font problems with Khmer. Only tested on Java 8/Windows 7 right now.

  • Property svn:eol-style set to native
File size: 15.1 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.BasicStroke;
7import java.awt.Component;
8import java.awt.Container;
9import java.awt.Dialog;
10import java.awt.Dimension;
11import java.awt.Font;
12import java.awt.GraphicsEnvironment;
13import java.awt.GridBagLayout;
14import java.awt.Image;
15import java.awt.Stroke;
16import java.awt.Toolkit;
17import java.awt.Window;
18import java.awt.event.ActionListener;
19import java.awt.event.HierarchyEvent;
20import java.awt.event.HierarchyListener;
21import java.awt.event.KeyEvent;
22import java.awt.image.FilteredImageSource;
23import java.lang.reflect.InvocationTargetException;
24import java.util.Arrays;
25import java.util.Enumeration;
26import java.util.List;
27import java.util.concurrent.Callable;
28import java.util.concurrent.ExecutionException;
29import java.util.concurrent.FutureTask;
30
31import javax.swing.GrayFilter;
32import javax.swing.Icon;
33import javax.swing.ImageIcon;
34import javax.swing.JComponent;
35import javax.swing.JLabel;
36import javax.swing.JOptionPane;
37import javax.swing.JPanel;
38import javax.swing.JScrollPane;
39import javax.swing.SwingUtilities;
40import javax.swing.Timer;
41import javax.swing.UIManager;
42import javax.swing.plaf.FontUIResource;
43
44import org.openstreetmap.josm.Main;
45import org.openstreetmap.josm.gui.ExtendedDialog;
46import org.openstreetmap.josm.gui.widgets.HtmlPanel;
47import org.openstreetmap.josm.tools.CheckParameterUtil;
48import org.openstreetmap.josm.tools.GBC;
49import org.openstreetmap.josm.tools.ImageProvider;
50
51/**
52 * basic gui utils
53 */
54public final class GuiHelper {
55
56 private GuiHelper() {
57 // Hide default constructor for utils classes
58 }
59
60 /**
61 * disable / enable a component and all its child components
62 */
63 public static void setEnabledRec(Container root, boolean enabled) {
64 root.setEnabled(enabled);
65 Component[] children = root.getComponents();
66 for (Component child : children) {
67 if(child instanceof Container) {
68 setEnabledRec((Container) child, enabled);
69 } else {
70 child.setEnabled(enabled);
71 }
72 }
73 }
74
75 public static void executeByMainWorkerInEDT(final Runnable task) {
76 Main.worker.submit(new Runnable() {
77 @Override
78 public void run() {
79 runInEDTAndWait(task);
80 }
81 });
82 }
83
84 /**
85 * Executes asynchronously a runnable in
86 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
87 * @param task The runnable to execute
88 * @see SwingUtilities#invokeLater
89 */
90 public static void runInEDT(Runnable task) {
91 if (SwingUtilities.isEventDispatchThread()) {
92 task.run();
93 } else {
94 SwingUtilities.invokeLater(task);
95 }
96 }
97
98 /**
99 * Executes synchronously a runnable in
100 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
101 * @param task The runnable to execute
102 * @see SwingUtilities#invokeAndWait
103 */
104 public static void runInEDTAndWait(Runnable task) {
105 if (SwingUtilities.isEventDispatchThread()) {
106 task.run();
107 } else {
108 try {
109 SwingUtilities.invokeAndWait(task);
110 } catch (InterruptedException | InvocationTargetException e) {
111 Main.error(e);
112 }
113 }
114 }
115
116 /**
117 * Executes synchronously a callable in
118 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>
119 * and return a value.
120 * @param callable The callable to execute
121 * @return The computed result
122 * @since 7204
123 */
124 public static <V> V runInEDTAndWaitAndReturn(Callable<V> callable) {
125 if (SwingUtilities.isEventDispatchThread()) {
126 try {
127 return callable.call();
128 } catch (Exception e) {
129 Main.error(e);
130 return null;
131 }
132 } else {
133 FutureTask<V> task = new FutureTask<V>(callable);
134 SwingUtilities.invokeLater(task);
135 try {
136 return task.get();
137 } catch (InterruptedException | ExecutionException e) {
138 Main.error(e);
139 return null;
140 }
141 }
142 }
143
144 /**
145 * Warns user about a dangerous action requiring confirmation.
146 * @param title Title of dialog
147 * @param content Content of dialog
148 * @param baseActionIcon Unused? FIXME why is this parameter unused?
149 * @param continueToolTip Tooltip to display for "continue" button
150 * @return true if the user wants to cancel, false if they want to continue
151 */
152 public static final boolean warnUser(String title, String content, ImageIcon baseActionIcon, String continueToolTip) {
153 ExtendedDialog dlg = new ExtendedDialog(Main.parent,
154 title, new String[] {tr("Cancel"), tr("Continue")});
155 dlg.setContent(content);
156 dlg.setButtonIcons(new Icon[] {
157 ImageProvider.get("cancel"),
158 ImageProvider.overlay(
159 ImageProvider.get("upload"),
160 new ImageIcon(ImageProvider.get("warning-small").getImage().getScaledInstance(10 , 10, Image.SCALE_SMOOTH)),
161 ImageProvider.OverlayPosition.SOUTHEAST)});
162 dlg.setToolTipTexts(new String[] {
163 tr("Cancel"),
164 continueToolTip});
165 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
166 dlg.setCancelButton(1);
167 return dlg.showDialog().getValue() != 2;
168 }
169
170 /**
171 * Notifies user about an error received from an external source as an HTML page.
172 * @param parent Parent component
173 * @param title Title of dialog
174 * @param message Message displayed at the top of the dialog
175 * @param html HTML content to display (real error message)
176 * @since 7312
177 */
178 public static final void notifyUserHtmlError(Component parent, String title, String message, String html) {
179 JPanel p = new JPanel(new GridBagLayout());
180 p.add(new JLabel(message), GBC.eol());
181 p.add(new JLabel(tr("Received error page:")), GBC.eol());
182 JScrollPane sp = embedInVerticalScrollPane(new HtmlPanel(html));
183 sp.setPreferredSize(new Dimension(640, 240));
184 p.add(sp, GBC.eol().fill(GBC.BOTH));
185
186 ExtendedDialog ed = new ExtendedDialog(parent, title, new String[] {tr("OK")});
187 ed.setButtonIcons(new String[] {"ok.png"});
188 ed.setContent(p);
189 ed.showDialog();
190 }
191
192 /**
193 * Replies the disabled (grayed) version of the specified image.
194 * @param image The image to disable
195 * @return The disabled (grayed) version of the specified image, brightened by 20%.
196 * @since 5484
197 */
198 public static final Image getDisabledImage(Image image) {
199 return Toolkit.getDefaultToolkit().createImage(
200 new FilteredImageSource(image.getSource(), new GrayFilter(true, 20)));
201 }
202
203 /**
204 * Replies the disabled (grayed) version of the specified icon.
205 * @param icon The icon to disable
206 * @return The disabled (grayed) version of the specified icon, brightened by 20%.
207 * @since 5484
208 */
209 public static final ImageIcon getDisabledIcon(ImageIcon icon) {
210 return new ImageIcon(getDisabledImage(icon.getImage()));
211 }
212
213 /**
214 * Attaches a {@code HierarchyListener} to the specified {@code Component} that
215 * will set its parent dialog resizeable. Use it before a call to JOptionPane#showXXXXDialog
216 * to make it resizeable.
217 * @param pane The component that will be displayed
218 * @param minDimension The minimum dimension that will be set for the dialog. Ignored if null
219 * @return {@code pane}
220 * @since 5493
221 */
222 public static final Component prepareResizeableOptionPane(final Component pane, final Dimension minDimension) {
223 if (pane != null) {
224 pane.addHierarchyListener(new HierarchyListener() {
225 @Override
226 public void hierarchyChanged(HierarchyEvent e) {
227 Window window = SwingUtilities.getWindowAncestor(pane);
228 if (window instanceof Dialog) {
229 Dialog dialog = (Dialog)window;
230 if (!dialog.isResizable()) {
231 dialog.setResizable(true);
232 if (minDimension != null) {
233 dialog.setMinimumSize(minDimension);
234 }
235 }
236 }
237 }
238 });
239 }
240 return pane;
241 }
242
243 /**
244 * Schedules a new Timer to be run in the future (once or several times).
245 * @param initialDelay milliseconds for the initial and between-event delay if repeatable
246 * @param actionListener an initial listener; can be null
247 * @param repeats specify false to make the timer stop after sending its first action event
248 * @return The (started) timer.
249 * @since 5735
250 */
251 public static final Timer scheduleTimer(int initialDelay, ActionListener actionListener, boolean repeats) {
252 Timer timer = new Timer(initialDelay, actionListener);
253 timer.setRepeats(repeats);
254 timer.start();
255 return timer;
256 }
257
258 /**
259 * Return s new BasicStroke object with given thickness and style
260 * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
261 * @return stroke for drawing
262 */
263 public static Stroke getCustomizedStroke(String code) {
264 String[] s = code.trim().split("[^\\.0-9]+");
265
266 if (s.length==0) return new BasicStroke();
267 float w;
268 try {
269 w = Float.parseFloat(s[0]);
270 } catch (NumberFormatException ex) {
271 w = 1.0f;
272 }
273 if (s.length>1) {
274 float[] dash= new float[s.length-1];
275 float sumAbs = 0;
276 try {
277 for (int i=0; i<s.length-1; i++) {
278 dash[i] = Float.parseFloat(s[i+1]);
279 sumAbs += Math.abs(dash[i]);
280 }
281 } catch (NumberFormatException ex) {
282 Main.error("Error in stroke preference format: "+code);
283 dash = new float[]{5.0f};
284 }
285 if (sumAbs < 1e-1) {
286 Main.error("Error in stroke dash fomat (all zeros): "+code);
287 return new BasicStroke(w);
288 }
289 // dashed stroke
290 return new BasicStroke(w, BasicStroke.CAP_BUTT,
291 BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
292 } else {
293 if (w>1) {
294 // thick stroke
295 return new BasicStroke(w, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
296 } else {
297 // thin stroke
298 return new BasicStroke(w);
299 }
300 }
301 }
302
303 /**
304 * Gets the font used to display monospaced text in a component, if possible.
305 * @param component The component
306 * @return the font used to display monospaced text in a component, if possible
307 * @since 7896
308 */
309 public static Font getMonospacedFont(JComponent component) {
310 // Special font for Khmer script
311 if ("km".equals(Main.pref.get("language"))) {
312 return component.getFont();
313 } else {
314 return new Font("Monospaced", component.getFont().getStyle(), component.getFont().getSize());
315 }
316 }
317
318 /**
319 * Gets the font used to display JOSM title in about dialog and splash screen.
320 * @return By order or priority, the first font available in local fonts:
321 * 1. Helvetica Bold 20
322 * 2. Calibri Bold 23
323 * 3. Arial Bold 20
324 * 4. SansSerif Bold 20
325 * Except if current language is Khmer, where it will be current font at size 20
326 * @since 5797
327 */
328 public static Font getTitleFont() {
329 List<String> fonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
330 // Special font for Khmer script
331 if ("km".equals(Main.pref.get("language"))) {
332 return UIManager.getFont("Label.font").deriveFont(20.0f);
333 }
334 // Helvetica is the preferred choice but is not available by default on Windows
335 // (https://www.microsoft.com/typography/fonts/product.aspx?pid=161)
336 if (fonts.contains("Helvetica")) {
337 return new Font("Helvetica", Font.BOLD, 20);
338 // Calibri is the default Windows font since Windows Vista but is not available on older versions of Windows, where Arial is preferred
339 } else if (fonts.contains("Calibri")) {
340 return new Font("Calibri", Font.BOLD, 23);
341 } else if (fonts.contains("Arial")) {
342 return new Font("Arial", Font.BOLD, 20);
343 // No luck, nothing found, fallback to one of the 5 fonts provided with Java (Serif, SansSerif, Monospaced, Dialog, and DialogInput)
344 } else {
345 return new Font("SansSerif", Font.BOLD, 20);
346 }
347 }
348
349 /**
350 * Embeds the given component into a new vertical-only scrollable {@code JScrollPane}.
351 * @param panel The component to embed
352 * @return the vertical scrollable {@code JScrollPane}
353 * @since 6666
354 */
355 public static JScrollPane embedInVerticalScrollPane(Component panel) {
356 return new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
357 }
358
359 /**
360 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts.
361 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but:
362 * <ul>
363 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended
364 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li>
365 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li>
366 * </ul>
367 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts
368 * @since 7539
369 */
370 public static int getMenuShortcutKeyMaskEx() {
371 return Main.isPlatformOsx() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;
372 }
373
374 /**
375 * Sets a global font for all UI, replacing default font of current look and feel.
376 * @param name Font name. It is up to the caller to make sure the font exists
377 * @since 7896
378 * @throws IllegalArgumentException if name is null
379 */
380 public static void setUIFont(String name) {
381 CheckParameterUtil.ensureParameterNotNull(name, "name");
382 Main.info("Setting "+name+" as the default UI font");
383 Enumeration<?> keys = UIManager.getDefaults().keys();
384 while (keys.hasMoreElements()) {
385 Object key = keys.nextElement();
386 Object value = UIManager.get(key);
387 if (value != null && value instanceof FontUIResource) {
388 FontUIResource fui = (FontUIResource)value;
389 UIManager.put(key, new FontUIResource(name, fui.getStyle(), fui.getSize()));
390 }
391 }
392 }
393}
Note: See TracBrowser for help on using the repository browser.