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

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

sonar - squid:S3878 - Arrays should not be created for varargs parameters

  • Property svn:eol-style set to native
File size: 20.0 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.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.image.FilteredImageSource;
27import java.lang.reflect.InvocationTargetException;
28import java.util.Enumeration;
29import java.util.EventObject;
30import java.util.concurrent.Callable;
31import java.util.concurrent.ExecutionException;
32import java.util.concurrent.FutureTask;
33
34import javax.swing.GrayFilter;
35import javax.swing.ImageIcon;
36import javax.swing.JComponent;
37import javax.swing.JLabel;
38import javax.swing.JOptionPane;
39import javax.swing.JPanel;
40import javax.swing.JPopupMenu;
41import javax.swing.JScrollPane;
42import javax.swing.Scrollable;
43import javax.swing.SwingUtilities;
44import javax.swing.Timer;
45import javax.swing.ToolTipManager;
46import javax.swing.UIManager;
47import javax.swing.plaf.FontUIResource;
48
49import org.openstreetmap.josm.Main;
50import org.openstreetmap.josm.data.preferences.StrokeProperty;
51import org.openstreetmap.josm.gui.ExtendedDialog;
52import org.openstreetmap.josm.gui.widgets.HtmlPanel;
53import org.openstreetmap.josm.tools.CheckParameterUtil;
54import org.openstreetmap.josm.tools.ColorHelper;
55import org.openstreetmap.josm.tools.GBC;
56import org.openstreetmap.josm.tools.ImageOverlay;
57import org.openstreetmap.josm.tools.ImageProvider;
58import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
59import org.openstreetmap.josm.tools.LanguageInfo;
60import org.openstreetmap.josm.tools.bugreport.BugReport;
61import org.openstreetmap.josm.tools.bugreport.ReportedException;
62
63/**
64 * basic gui utils
65 */
66public final class GuiHelper {
67
68 private GuiHelper() {
69 // Hide default constructor for utils classes
70 }
71
72 /**
73 * disable / enable a component and all its child components
74 * @param root component
75 * @param enabled enabled state
76 */
77 public static void setEnabledRec(Container root, boolean enabled) {
78 root.setEnabled(enabled);
79 Component[] children = root.getComponents();
80 for (Component child : children) {
81 if (child instanceof Container) {
82 setEnabledRec((Container) child, enabled);
83 } else {
84 child.setEnabled(enabled);
85 }
86 }
87 }
88
89 public static void executeByMainWorkerInEDT(final Runnable task) {
90 Main.worker.submit(() -> runInEDTAndWait(task));
91 }
92
93 /**
94 * Executes asynchronously a runnable in
95 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
96 * @param task The runnable to execute
97 * @see SwingUtilities#invokeLater
98 */
99 public static void runInEDT(Runnable task) {
100 if (SwingUtilities.isEventDispatchThread()) {
101 task.run();
102 } else {
103 SwingUtilities.invokeLater(task);
104 }
105 }
106
107 /**
108 * Executes synchronously a runnable in
109 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
110 * @param task The runnable to execute
111 * @see SwingUtilities#invokeAndWait
112 */
113 public static void runInEDTAndWait(Runnable task) {
114 if (SwingUtilities.isEventDispatchThread()) {
115 task.run();
116 } else {
117 try {
118 SwingUtilities.invokeAndWait(task);
119 } catch (InterruptedException | InvocationTargetException e) {
120 Main.error(e);
121 }
122 }
123 }
124
125 /**
126 * Executes synchronously a runnable in
127 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>.
128 * <p>
129 * Passes on the exception that was thrown to the thread calling this.
130 * The exception is wrapped using a {@link ReportedException}.
131 * @param task The runnable to execute
132 * @see SwingUtilities#invokeAndWait
133 * @since 10271
134 */
135 public static void runInEDTAndWaitWithException(Runnable task) {
136 if (SwingUtilities.isEventDispatchThread()) {
137 task.run();
138 } else {
139 try {
140 SwingUtilities.invokeAndWait(task);
141 } catch (InterruptedException | InvocationTargetException e) {
142 throw BugReport.intercept(e).put("task", task);
143 }
144 }
145 }
146
147 /**
148 * Executes synchronously a callable in
149 * <a href="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html">Event Dispatch Thread</a>
150 * and return a value.
151 * @param <V> the result type of method <tt>call</tt>
152 * @param callable The callable to execute
153 * @return The computed result
154 * @since 7204
155 */
156 public static <V> V runInEDTAndWaitAndReturn(Callable<V> callable) {
157 if (SwingUtilities.isEventDispatchThread()) {
158 try {
159 return callable.call();
160 } catch (Exception e) { // NOPMD
161 Main.error(e);
162 return null;
163 }
164 } else {
165 FutureTask<V> task = new FutureTask<>(callable);
166 SwingUtilities.invokeLater(task);
167 try {
168 return task.get();
169 } catch (InterruptedException | ExecutionException e) {
170 Main.error(e);
171 return null;
172 }
173 }
174 }
175
176 /**
177 * This function fails if it was not called from the EDT thread.
178 * @throws IllegalStateException if called from wrong thread.
179 * @since 10271
180 */
181 public static void assertCallFromEdt() {
182 if (!SwingUtilities.isEventDispatchThread()) {
183 throw new IllegalStateException(
184 "Needs to be called from the EDT thread, not from " + Thread.currentThread().getName());
185 }
186 }
187
188 /**
189 * Warns user about a dangerous action requiring confirmation.
190 * @param title Title of dialog
191 * @param content Content of dialog
192 * @param baseActionIcon Unused? FIXME why is this parameter unused?
193 * @param continueToolTip Tooltip to display for "continue" button
194 * @return true if the user wants to cancel, false if they want to continue
195 */
196 public static boolean warnUser(String title, String content, ImageIcon baseActionIcon, String continueToolTip) {
197 ExtendedDialog dlg = new ExtendedDialog(Main.parent,
198 title, tr("Cancel"), tr("Continue"));
199 dlg.setContent(content);
200 dlg.setButtonIcons(
201 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
202 new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
203 new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get());
204 dlg.setToolTipTexts(tr("Cancel"), continueToolTip);
205 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
206 dlg.setCancelButton(1);
207 return dlg.showDialog().getValue() != 2;
208 }
209
210 /**
211 * Notifies user about an error received from an external source as an HTML page.
212 * @param parent Parent component
213 * @param title Title of dialog
214 * @param message Message displayed at the top of the dialog
215 * @param html HTML content to display (real error message)
216 * @since 7312
217 */
218 public static void notifyUserHtmlError(Component parent, String title, String message, String html) {
219 JPanel p = new JPanel(new GridBagLayout());
220 p.add(new JLabel(message), GBC.eol());
221 p.add(new JLabel(tr("Received error page:")), GBC.eol());
222 JScrollPane sp = embedInVerticalScrollPane(new HtmlPanel(html));
223 sp.setPreferredSize(new Dimension(640, 240));
224 p.add(sp, GBC.eol().fill(GBC.BOTH));
225
226 ExtendedDialog ed = new ExtendedDialog(parent, title, tr("OK"));
227 ed.setButtonIcons("ok");
228 ed.setContent(p);
229 ed.showDialog();
230 }
231
232 /**
233 * Replies the disabled (grayed) version of the specified image.
234 * @param image The image to disable
235 * @return The disabled (grayed) version of the specified image, brightened by 20%.
236 * @since 5484
237 */
238 public static Image getDisabledImage(Image image) {
239 return Toolkit.getDefaultToolkit().createImage(
240 new FilteredImageSource(image.getSource(), new GrayFilter(true, 20)));
241 }
242
243 /**
244 * Replies the disabled (grayed) version of the specified icon.
245 * @param icon The icon to disable
246 * @return The disabled (grayed) version of the specified icon, brightened by 20%.
247 * @since 5484
248 */
249 public static ImageIcon getDisabledIcon(ImageIcon icon) {
250 return new ImageIcon(getDisabledImage(icon.getImage()));
251 }
252
253 /**
254 * Attaches a {@code HierarchyListener} to the specified {@code Component} that
255 * will set its parent dialog resizeable. Use it before a call to JOptionPane#showXXXXDialog
256 * to make it resizeable.
257 * @param pane The component that will be displayed
258 * @param minDimension The minimum dimension that will be set for the dialog. Ignored if null
259 * @return {@code pane}
260 * @since 5493
261 */
262 public static Component prepareResizeableOptionPane(final Component pane, final Dimension minDimension) {
263 if (pane != null) {
264 pane.addHierarchyListener(e -> {
265 Window window = SwingUtilities.getWindowAncestor(pane);
266 if (window instanceof Dialog) {
267 Dialog dialog = (Dialog) window;
268 if (!dialog.isResizable()) {
269 dialog.setResizable(true);
270 if (minDimension != null) {
271 dialog.setMinimumSize(minDimension);
272 }
273 }
274 }
275 });
276 }
277 return pane;
278 }
279
280 /**
281 * Schedules a new Timer to be run in the future (once or several times).
282 * @param initialDelay milliseconds for the initial and between-event delay if repeatable
283 * @param actionListener an initial listener; can be null
284 * @param repeats specify false to make the timer stop after sending its first action event
285 * @return The (started) timer.
286 * @since 5735
287 */
288 public static Timer scheduleTimer(int initialDelay, ActionListener actionListener, boolean repeats) {
289 Timer timer = new Timer(initialDelay, actionListener);
290 timer.setRepeats(repeats);
291 timer.start();
292 return timer;
293 }
294
295 /**
296 * Return s new BasicStroke object with given thickness and style
297 * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
298 * @return stroke for drawing
299 * @see StrokeProperty
300 */
301 public static Stroke getCustomizedStroke(String code) {
302 return StrokeProperty.getFromString(code);
303 }
304
305 /**
306 * Gets the font used to display monospaced text in a component, if possible.
307 * @param component The component
308 * @return the font used to display monospaced text in a component, if possible
309 * @since 7896
310 */
311 public static Font getMonospacedFont(JComponent component) {
312 // Special font for Khmer script
313 if ("km".equals(LanguageInfo.getJOSMLocaleCode())) {
314 return component.getFont();
315 } else {
316 return new Font("Monospaced", component.getFont().getStyle(), component.getFont().getSize());
317 }
318 }
319
320 /**
321 * Gets the font used to display JOSM title in about dialog and splash screen.
322 * @return title font
323 * @since 5797
324 */
325 public static Font getTitleFont() {
326 return new Font("SansSerif", Font.BOLD, 23);
327 }
328
329 /**
330 * Embeds the given component into a new vertical-only scrollable {@code JScrollPane}.
331 * @param panel The component to embed
332 * @return the vertical scrollable {@code JScrollPane}
333 * @since 6666
334 */
335 public static JScrollPane embedInVerticalScrollPane(Component panel) {
336 return new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
337 }
338
339 /**
340 * Set the default unit increment for a {@code JScrollPane}.
341 *
342 * This fixes slow mouse wheel scrolling when the content of the {@code JScrollPane}
343 * is a {@code JPanel} or other component that does not implement the {@link Scrollable}
344 * interface.
345 * The default unit increment is 1 pixel. Multiplied by the number of unit increments
346 * per mouse wheel "click" (platform dependent, usually 3), this makes a very
347 * sluggish mouse wheel experience.
348 * This methods sets the unit increment to a larger, more reasonable value.
349 * @param sp the scroll pane
350 * @return the scroll pane (same object) with fixed unit increment
351 * @throws IllegalArgumentException if the component inside of the scroll pane
352 * implements the {@code Scrollable} interface ({@code JTree}, {@code JLayer},
353 * {@code JList}, {@code JTextComponent} and {@code JTable})
354 */
355 public static JScrollPane setDefaultIncrement(JScrollPane sp) {
356 if (sp.getViewport().getView() instanceof Scrollable) {
357 throw new IllegalArgumentException();
358 }
359 sp.getVerticalScrollBar().setUnitIncrement(10);
360 sp.getHorizontalScrollBar().setUnitIncrement(10);
361 return sp;
362 }
363
364 /**
365 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts.
366 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but:
367 * <ul>
368 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended
369 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li>
370 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li>
371 * </ul>
372 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts
373 * @since 7539
374 */
375 public static int getMenuShortcutKeyMaskEx() {
376 return Main.isPlatformOsx() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;
377 }
378
379 /**
380 * Sets a global font for all UI, replacing default font of current look and feel.
381 * @param name Font name. It is up to the caller to make sure the font exists
382 * @throws IllegalArgumentException if name is null
383 * @since 7896
384 */
385 public static void setUIFont(String name) {
386 CheckParameterUtil.ensureParameterNotNull(name, "name");
387 Main.info("Setting "+name+" as the default UI font");
388 Enumeration<?> keys = UIManager.getDefaults().keys();
389 while (keys.hasMoreElements()) {
390 Object key = keys.nextElement();
391 Object value = UIManager.get(key);
392 if (value instanceof FontUIResource) {
393 FontUIResource fui = (FontUIResource) value;
394 UIManager.put(key, new FontUIResource(name, fui.getStyle(), fui.getSize()));
395 }
396 }
397 }
398
399 /**
400 * Sets the background color for this component, and adjust the foreground color so the text remains readable.
401 * @param c component
402 * @param background background color
403 * @since 9223
404 */
405 public static void setBackgroundReadable(JComponent c, Color background) {
406 c.setBackground(background);
407 c.setForeground(ColorHelper.getForegroundColor(background));
408 }
409
410 /**
411 * Gets the size of the screen. On systems with multiple displays, the primary display is used.
412 * This method returns always 800x600 in headless mode (useful for unit tests).
413 * @return the size of this toolkit's screen, in pixels, or 800x600
414 * @see Toolkit#getScreenSize
415 * @since 9576
416 */
417 public static Dimension getScreenSize() {
418 return GraphicsEnvironment.isHeadless() ? new Dimension(800, 600) : Toolkit.getDefaultToolkit().getScreenSize();
419 }
420
421 /**
422 * Gets the size of the screen. On systems with multiple displays,
423 * contrary to {@link #getScreenSize()}, the biggest display is used.
424 * This method returns always 800x600 in headless mode (useful for unit tests).
425 * @return the size of maximum screen, in pixels, or 800x600
426 * @see Toolkit#getScreenSize
427 * @since 10470
428 */
429 public static Dimension getMaximumScreenSize() {
430 if (GraphicsEnvironment.isHeadless()) {
431 return new Dimension(800, 600);
432 }
433
434 int height = 0;
435 int width = 0;
436 for (GraphicsDevice gd: GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
437 DisplayMode dm = gd.getDisplayMode();
438 height = Math.max(height, dm.getHeight());
439 width = Math.max(width, dm.getWidth());
440 }
441 if (height == 0 || width == 0) {
442 return new Dimension(800, 600);
443 }
444 return new Dimension(width, height);
445 }
446
447 /**
448 * Returns the first <code>Window</code> ancestor of event source, or
449 * {@code null} if event source is not a component contained inside a <code>Window</code>.
450 * @param e event object
451 * @return a Window, or {@code null}
452 * @since 9916
453 */
454 public static Window getWindowAncestorFor(EventObject e) {
455 if (e != null) {
456 Object source = e.getSource();
457 if (source instanceof Component) {
458 Window ancestor = SwingUtilities.getWindowAncestor((Component) source);
459 if (ancestor != null) {
460 return ancestor;
461 } else {
462 Container parent = ((Component) source).getParent();
463 if (parent instanceof JPopupMenu) {
464 Component invoker = ((JPopupMenu) parent).getInvoker();
465 return SwingUtilities.getWindowAncestor(invoker);
466 }
467 }
468 }
469 }
470 return null;
471 }
472
473 /**
474 * Extends tooltip dismiss delay to a default value of 1 minute for the given component.
475 * @param c component
476 * @since 10024
477 */
478 public static void extendTooltipDelay(Component c) {
479 extendTooltipDelay(c, 60_000);
480 }
481
482 /**
483 * Extends tooltip dismiss delay to the specified value for the given component.
484 * @param c component
485 * @param delay tooltip dismiss delay in milliseconds
486 * @see <a href="http://stackoverflow.com/a/6517902/2257172">http://stackoverflow.com/a/6517902/2257172</a>
487 * @since 10024
488 */
489 public static void extendTooltipDelay(Component c, final int delay) {
490 final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
491 c.addMouseListener(new MouseAdapter() {
492 @Override
493 public void mouseEntered(MouseEvent me) {
494 ToolTipManager.sharedInstance().setDismissDelay(delay);
495 }
496
497 @Override
498 public void mouseExited(MouseEvent me) {
499 ToolTipManager.sharedInstance().setDismissDelay(defaultDismissTimeout);
500 }
501 });
502 }
503
504 /**
505 * Returns the specified component's <code>Frame</code> without throwing exception in headless mode.
506 *
507 * @param parentComponent the <code>Component</code> to check for a <code>Frame</code>
508 * @return the <code>Frame</code> that contains the component, or <code>getRootFrame</code>
509 * if the component is <code>null</code>, or does not have a valid <code>Frame</code> parent
510 * @see JOptionPane#getFrameForComponent
511 * @see GraphicsEnvironment#isHeadless
512 * @since 10035
513 */
514 public static Frame getFrameForComponent(Component parentComponent) {
515 try {
516 return JOptionPane.getFrameForComponent(parentComponent);
517 } catch (HeadlessException e) {
518 Main.debug(e);
519 return null;
520 }
521 }
522}
Note: See TracBrowser for help on using the repository browser.