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

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

Sonar - squid:S2293 - The diamond operator ("<>") should be used

  • Property svn:eol-style set to native
File size: 13.8 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.GridBagLayout;
13import java.awt.Image;
14import java.awt.Stroke;
15import java.awt.Toolkit;
16import java.awt.Window;
17import java.awt.event.ActionListener;
18import java.awt.event.HierarchyEvent;
19import java.awt.event.HierarchyListener;
20import java.awt.event.KeyEvent;
21import java.awt.image.FilteredImageSource;
22import java.lang.reflect.InvocationTargetException;
23import java.util.Enumeration;
24import java.util.concurrent.Callable;
25import java.util.concurrent.ExecutionException;
26import java.util.concurrent.FutureTask;
27
28import javax.swing.GrayFilter;
29import javax.swing.Icon;
30import javax.swing.ImageIcon;
31import javax.swing.JComponent;
32import javax.swing.JLabel;
33import javax.swing.JOptionPane;
34import javax.swing.JPanel;
35import javax.swing.JScrollPane;
36import javax.swing.SwingUtilities;
37import javax.swing.Timer;
38import javax.swing.UIManager;
39import javax.swing.plaf.FontUIResource;
40
41import org.openstreetmap.josm.Main;
42import org.openstreetmap.josm.gui.ExtendedDialog;
43import org.openstreetmap.josm.gui.widgets.HtmlPanel;
44import org.openstreetmap.josm.tools.CheckParameterUtil;
45import org.openstreetmap.josm.tools.GBC;
46import org.openstreetmap.josm.tools.ImageOverlay;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
49import org.openstreetmap.josm.tools.LanguageInfo;
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<>(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 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 new ImageProvider("cancel").setMaxSize(ImageSizes.LARGEICON).get(),
158 new ImageProvider("upload").setMaxSize(ImageSizes.LARGEICON).addOverlay(
159 new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0)).get()});
160 dlg.setToolTipTexts(new String[] {
161 tr("Cancel"),
162 continueToolTip});
163 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
164 dlg.setCancelButton(1);
165 return dlg.showDialog().getValue() != 2;
166 }
167
168 /**
169 * Notifies user about an error received from an external source as an HTML page.
170 * @param parent Parent component
171 * @param title Title of dialog
172 * @param message Message displayed at the top of the dialog
173 * @param html HTML content to display (real error message)
174 * @since 7312
175 */
176 public static void notifyUserHtmlError(Component parent, String title, String message, String html) {
177 JPanel p = new JPanel(new GridBagLayout());
178 p.add(new JLabel(message), GBC.eol());
179 p.add(new JLabel(tr("Received error page:")), GBC.eol());
180 JScrollPane sp = embedInVerticalScrollPane(new HtmlPanel(html));
181 sp.setPreferredSize(new Dimension(640, 240));
182 p.add(sp, GBC.eol().fill(GBC.BOTH));
183
184 ExtendedDialog ed = new ExtendedDialog(parent, title, new String[] {tr("OK")});
185 ed.setButtonIcons(new String[] {"ok.png"});
186 ed.setContent(p);
187 ed.showDialog();
188 }
189
190 /**
191 * Replies the disabled (grayed) version of the specified image.
192 * @param image The image to disable
193 * @return The disabled (grayed) version of the specified image, brightened by 20%.
194 * @since 5484
195 */
196 public static Image getDisabledImage(Image image) {
197 return Toolkit.getDefaultToolkit().createImage(
198 new FilteredImageSource(image.getSource(), new GrayFilter(true, 20)));
199 }
200
201 /**
202 * Replies the disabled (grayed) version of the specified icon.
203 * @param icon The icon to disable
204 * @return The disabled (grayed) version of the specified icon, brightened by 20%.
205 * @since 5484
206 */
207 public static ImageIcon getDisabledIcon(ImageIcon icon) {
208 return new ImageIcon(getDisabledImage(icon.getImage()));
209 }
210
211 /**
212 * Attaches a {@code HierarchyListener} to the specified {@code Component} that
213 * will set its parent dialog resizeable. Use it before a call to JOptionPane#showXXXXDialog
214 * to make it resizeable.
215 * @param pane The component that will be displayed
216 * @param minDimension The minimum dimension that will be set for the dialog. Ignored if null
217 * @return {@code pane}
218 * @since 5493
219 */
220 public static Component prepareResizeableOptionPane(final Component pane, final Dimension minDimension) {
221 if (pane != null) {
222 pane.addHierarchyListener(new HierarchyListener() {
223 @Override
224 public void hierarchyChanged(HierarchyEvent e) {
225 Window window = SwingUtilities.getWindowAncestor(pane);
226 if (window instanceof Dialog) {
227 Dialog dialog = (Dialog) window;
228 if (!dialog.isResizable()) {
229 dialog.setResizable(true);
230 if (minDimension != null) {
231 dialog.setMinimumSize(minDimension);
232 }
233 }
234 }
235 }
236 });
237 }
238 return pane;
239 }
240
241 /**
242 * Schedules a new Timer to be run in the future (once or several times).
243 * @param initialDelay milliseconds for the initial and between-event delay if repeatable
244 * @param actionListener an initial listener; can be null
245 * @param repeats specify false to make the timer stop after sending its first action event
246 * @return The (started) timer.
247 * @since 5735
248 */
249 public static Timer scheduleTimer(int initialDelay, ActionListener actionListener, boolean repeats) {
250 Timer timer = new Timer(initialDelay, actionListener);
251 timer.setRepeats(repeats);
252 timer.start();
253 return timer;
254 }
255
256 /**
257 * Return s new BasicStroke object with given thickness and style
258 * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
259 * @return stroke for drawing
260 */
261 public static Stroke getCustomizedStroke(String code) {
262 String[] s = code.trim().split("[^\\.0-9]+");
263
264 if (s.length == 0) return new BasicStroke();
265 float w;
266 try {
267 w = Float.parseFloat(s[0]);
268 } catch (NumberFormatException ex) {
269 w = 1.0f;
270 }
271 if (s.length > 1) {
272 float[] dash = new float[s.length-1];
273 float sumAbs = 0;
274 try {
275 for (int i = 0; i < s.length-1; i++) {
276 dash[i] = Float.parseFloat(s[i+1]);
277 sumAbs += Math.abs(dash[i]);
278 }
279 } catch (NumberFormatException ex) {
280 Main.error("Error in stroke preference format: "+code);
281 dash = new float[]{5.0f};
282 }
283 if (sumAbs < 1e-1) {
284 Main.error("Error in stroke dash fomat (all zeros): "+code);
285 return new BasicStroke(w);
286 }
287 // dashed stroke
288 return new BasicStroke(w, BasicStroke.CAP_BUTT,
289 BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
290 } else {
291 if (w > 1) {
292 // thick stroke
293 return new BasicStroke(w, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
294 } else {
295 // thin stroke
296 return new BasicStroke(w);
297 }
298 }
299 }
300
301 /**
302 * Gets the font used to display monospaced text in a component, if possible.
303 * @param component The component
304 * @return the font used to display monospaced text in a component, if possible
305 * @since 7896
306 */
307 public static Font getMonospacedFont(JComponent component) {
308 // Special font for Khmer script
309 if ("km".equals(LanguageInfo.getJOSMLocaleCode())) {
310 return component.getFont();
311 } else {
312 return new Font("Monospaced", component.getFont().getStyle(), component.getFont().getSize());
313 }
314 }
315
316 /**
317 * Gets the font used to display JOSM title in about dialog and splash screen.
318 * @return title font
319 * @since 5797
320 */
321 public static Font getTitleFont() {
322 return new Font("SansSerif", Font.BOLD, 23);
323 }
324
325 /**
326 * Embeds the given component into a new vertical-only scrollable {@code JScrollPane}.
327 * @param panel The component to embed
328 * @return the vertical scrollable {@code JScrollPane}
329 * @since 6666
330 */
331 public static JScrollPane embedInVerticalScrollPane(Component panel) {
332 return new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
333 }
334
335 /**
336 * Returns extended modifier key used as the appropriate accelerator key for menu shortcuts.
337 * It is advised everywhere to use {@link Toolkit#getMenuShortcutKeyMask()} to get the cross-platform modifier, but:
338 * <ul>
339 * <li>it returns KeyEvent.CTRL_MASK instead of KeyEvent.CTRL_DOWN_MASK. We used the extended
340 * modifier for years, and Oracle recommends to use it instead, so it's best to keep it</li>
341 * <li>the method throws a HeadlessException ! So we would need to handle it for unit tests anyway</li>
342 * </ul>
343 * @return extended modifier key used as the appropriate accelerator key for menu shortcuts
344 * @since 7539
345 */
346 public static int getMenuShortcutKeyMaskEx() {
347 return Main.isPlatformOsx() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;
348 }
349
350 /**
351 * Sets a global font for all UI, replacing default font of current look and feel.
352 * @param name Font name. It is up to the caller to make sure the font exists
353 * @throws IllegalArgumentException if name is null
354 * @since 7896
355 */
356 public static void setUIFont(String name) {
357 CheckParameterUtil.ensureParameterNotNull(name, "name");
358 Main.info("Setting "+name+" as the default UI font");
359 Enumeration<?> keys = UIManager.getDefaults().keys();
360 while (keys.hasMoreElements()) {
361 Object key = keys.nextElement();
362 Object value = UIManager.get(key);
363 if (value instanceof FontUIResource) {
364 FontUIResource fui = (FontUIResource) value;
365 UIManager.put(key, new FontUIResource(name, fui.getStyle(), fui.getSize()));
366 }
367 }
368 }
369}
Note: See TracBrowser for help on using the repository browser.