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

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

see #10267 - display HTML error page sent by WMS server when applicable

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