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

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

Sonar: various code style cleanup:

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