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

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

code refactoring for unit tests / headless mode

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