source: josm/trunk/src/org/openstreetmap/josm/gui/HelpAwareOptionPane.java@ 11610

Last change on this file since 11610 was 10791, checked in by simon04, 8 years ago

see #13319 - Use InputMapUtils where applicable (VK_ESCAPE)

  • Property svn:eol-style set to native
File size: 13.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Dialog.ModalityType;
8import java.awt.GraphicsEnvironment;
9import java.awt.event.ActionEvent;
10import java.awt.event.WindowAdapter;
11import java.awt.event.WindowEvent;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.HashSet;
15import java.util.List;
16
17import javax.swing.AbstractAction;
18import javax.swing.Action;
19import javax.swing.Icon;
20import javax.swing.JButton;
21import javax.swing.JDialog;
22import javax.swing.JOptionPane;
23import javax.swing.event.ChangeEvent;
24import javax.swing.event.ChangeListener;
25
26import org.openstreetmap.josm.Main;
27import org.openstreetmap.josm.gui.help.HelpBrowser;
28import org.openstreetmap.josm.gui.help.HelpUtil;
29import org.openstreetmap.josm.gui.util.GuiHelper;
30import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
31import org.openstreetmap.josm.tools.ImageProvider;
32import org.openstreetmap.josm.tools.InputMapUtils;
33import org.openstreetmap.josm.tools.WindowGeometry;
34
35public final class HelpAwareOptionPane {
36
37 private HelpAwareOptionPane() {
38 // Hide default constructor for utils classes
39 }
40
41 public static class ButtonSpec {
42 public final String text;
43 public final Icon icon;
44 public final String tooltipText;
45 public final String helpTopic;
46 private boolean enabled;
47
48 private final Collection<ChangeListener> listeners = new HashSet<>();
49
50 /**
51 * Constructs a new {@code ButtonSpec}.
52 * @param text the button text
53 * @param icon the icon to display. Can be null
54 * @param tooltipText the tooltip text. Can be null.
55 * @param helpTopic the help topic. Can be null.
56 */
57 public ButtonSpec(String text, Icon icon, String tooltipText, String helpTopic) {
58 this(text, icon, tooltipText, helpTopic, true);
59 }
60
61 /**
62 * Constructs a new {@code ButtonSpec}.
63 * @param text the button text
64 * @param icon the icon to display. Can be null
65 * @param tooltipText the tooltip text. Can be null.
66 * @param helpTopic the help topic. Can be null.
67 * @param enabled the enabled status
68 * @since 5951
69 */
70 public ButtonSpec(String text, Icon icon, String tooltipText, String helpTopic, boolean enabled) {
71 this.text = text;
72 this.icon = icon;
73 this.tooltipText = tooltipText;
74 this.helpTopic = helpTopic;
75 setEnabled(enabled);
76 }
77
78 /**
79 * Determines if this button spec is enabled
80 * @return {@code true} if this button spec is enabled, {@code false} otherwise
81 * @since 6051
82 */
83 public final boolean isEnabled() {
84 return enabled;
85 }
86
87 /**
88 * Enables or disables this button spec, depending on the value of the parameter {@code b}.
89 * @param enabled if {@code true}, this button spec is enabled; otherwise this button spec is disabled
90 * @since 6051
91 */
92 public final void setEnabled(boolean enabled) {
93 if (this.enabled != enabled) {
94 this.enabled = enabled;
95 ChangeEvent event = new ChangeEvent(this);
96 for (ChangeListener listener : listeners) {
97 listener.stateChanged(event);
98 }
99 }
100 }
101
102 private boolean addChangeListener(ChangeListener listener) {
103 return listener != null && listeners.add(listener);
104 }
105 }
106
107 private static class DefaultAction extends AbstractAction {
108 private final JDialog dialog;
109 private final JOptionPane pane;
110 private final int value;
111
112 DefaultAction(JDialog dialog, JOptionPane pane, int value) {
113 this.dialog = dialog;
114 this.pane = pane;
115 this.value = value;
116 }
117
118 @Override
119 public void actionPerformed(ActionEvent e) {
120 pane.setValue(value);
121 dialog.setVisible(false);
122 }
123 }
124
125 /**
126 * Creates the list buttons to be displayed in the option pane dialog.
127 *
128 * @param options the option. If null, just creates an OK button and a help button
129 * @param helpTopic the help topic. The context sensitive help of all buttons is equal
130 * to the context sensitive help of the whole dialog
131 * @return the list of buttons
132 */
133 private static List<JButton> createOptionButtons(ButtonSpec[] options, String helpTopic) {
134 List<JButton> buttons = new ArrayList<>();
135 if (options == null) {
136 JButton b = new JButton(tr("OK"));
137 b.setIcon(ImageProvider.get("ok"));
138 b.setToolTipText(tr("Click to close the dialog"));
139 b.setFocusable(true);
140 buttons.add(b);
141 } else {
142 for (final ButtonSpec spec: options) {
143 final JButton b = new JButton(spec.text);
144 b.setIcon(spec.icon);
145 b.setToolTipText(spec.tooltipText == null ? "" : spec.tooltipText);
146 if (helpTopic != null) {
147 HelpUtil.setHelpContext(b, helpTopic);
148 }
149 b.setFocusable(true);
150 b.setEnabled(spec.isEnabled());
151 spec.addChangeListener(e -> b.setEnabled(spec.isEnabled()));
152 buttons.add(b);
153 }
154 }
155 return buttons;
156 }
157
158 /**
159 * Creates the help button
160 *
161 * @param helpTopic the help topic
162 * @return the help button
163 */
164 private static JButton createHelpButton(final String helpTopic) {
165 JButton b = new JButton(tr("Help"));
166 b.setIcon(ImageProvider.get("help"));
167 b.setToolTipText(tr("Show help information"));
168 HelpUtil.setHelpContext(b, helpTopic);
169 Action a = new AbstractAction() {
170 @Override
171 public void actionPerformed(ActionEvent e) {
172 HelpBrowser.setUrlForHelpTopic(helpTopic);
173 }
174 };
175 b.addActionListener(a);
176 InputMapUtils.enableEnter(b);
177 return b;
178 }
179
180 /**
181 * Displays an option dialog which is aware of a help context. If <code>helpTopic</code> isn't null,
182 * the dialog includes a "Help" button and launches the help browser if the user presses F1. If the
183 * user clicks on the "Help" button the option dialog remains open and JOSM launches the help
184 * browser.
185 *
186 * <code>helpTopic</code> is the trailing part of a JOSM online help URL, i.e. the part after the leading
187 * <code>https://josm.openstreetmap.de/wiki/Help</code>. It should start with a leading '/' and it
188 * may include an anchor after a '#'.
189 *
190 * <strong>Examples</strong>
191 * <ul>
192 * <li>/Dialogs/RelationEditor</li>
193 * <li>/Dialogs/RelationEditor#ConflictInData</li>
194 * </ul>
195 *
196 * In addition, the option buttons display JOSM icons, similar to ExtendedDialog.
197 *
198 * @param parentComponent the parent component
199 * @param msg the message
200 * @param title the title
201 * @param messageType the message type (see {@link JOptionPane})
202 * @param icon the icon to display. Can be null.
203 * @param options the list of options to display. Can be null.
204 * @param defaultOption the default option. Can be null.
205 * @param helpTopic the help topic. Can be null.
206 * @return the index of the selected option or {@link JOptionPane#CLOSED_OPTION}
207 */
208 public static int showOptionDialog(Component parentComponent, Object msg, String title, int messageType,
209 Icon icon, final ButtonSpec[] options, final ButtonSpec defaultOption, final String helpTopic) {
210 final List<JButton> buttons = createOptionButtons(options, helpTopic);
211 if (helpTopic != null) {
212 buttons.add(createHelpButton(helpTopic));
213 }
214
215 JButton defaultButton = null;
216 if (options != null && defaultOption != null) {
217 for (int i = 0; i < options.length; i++) {
218 if (options[i] == defaultOption) {
219 defaultButton = buttons.get(i);
220 break;
221 }
222 }
223 }
224
225 final JOptionPane pane = new JOptionPane(
226 msg instanceof String ? new JMultilineLabel((String) msg, true) : msg,
227 messageType,
228 JOptionPane.DEFAULT_OPTION,
229 icon,
230 buttons.toArray(),
231 defaultButton
232 );
233
234 // Log message. Useful for bug reports and unit tests
235 switch (messageType) {
236 case JOptionPane.ERROR_MESSAGE:
237 Main.error(title + " - " + msg);
238 break;
239 case JOptionPane.WARNING_MESSAGE:
240 Main.warn(title + " - " + msg);
241 break;
242 default:
243 Main.info(title + " - " + msg);
244 }
245
246 if (!GraphicsEnvironment.isHeadless()) {
247 doShowOptionDialog(parentComponent, title, options, defaultOption, helpTopic, buttons, pane);
248 }
249 return pane.getValue() instanceof Integer ? (Integer) pane.getValue() : JOptionPane.OK_OPTION;
250 }
251
252 private static void doShowOptionDialog(Component parentComponent, String title, final ButtonSpec[] options,
253 final ButtonSpec defaultOption, final String helpTopic, final List<JButton> buttons,
254 final JOptionPane pane) {
255 final JDialog dialog = new JDialog(
256 GuiHelper.getFrameForComponent(parentComponent),
257 title,
258 ModalityType.DOCUMENT_MODAL
259 );
260 dialog.setContentPane(pane);
261 dialog.addWindowListener(new WindowAdapter() {
262 @Override
263 public void windowClosing(WindowEvent e) {
264 pane.setValue(JOptionPane.CLOSED_OPTION);
265 super.windowClosed(e);
266 }
267
268 @Override
269 public void windowOpened(WindowEvent e) {
270 if (defaultOption != null && options != null && options.length > 0) {
271 int i;
272 for (i = 0; i < options.length; i++) {
273 if (options[i] == defaultOption) {
274 break;
275 }
276 }
277 if (i >= options.length) {
278 buttons.get(0).requestFocusInWindow();
279 }
280 buttons.get(i).requestFocusInWindow();
281 } else {
282 buttons.get(0).requestFocusInWindow();
283 }
284 }
285 });
286 InputMapUtils.addEscapeAction(dialog.getRootPane(), new AbstractAction() {
287 @Override
288 public void actionPerformed(ActionEvent e) {
289 pane.setValue(JOptionPane.CLOSED_OPTION);
290 dialog.setVisible(false);
291 }
292 });
293
294 if (options != null) {
295 for (int i = 0; i < options.length; i++) {
296 final DefaultAction action = new DefaultAction(dialog, pane, i);
297 buttons.get(i).addActionListener(action);
298 InputMapUtils.addEnterAction(buttons.get(i), action);
299 }
300 } else {
301 final DefaultAction action = new DefaultAction(dialog, pane, 0);
302 buttons.get(0).addActionListener(action);
303 InputMapUtils.addEnterAction(buttons.get(0), action);
304 }
305
306 dialog.pack();
307 WindowGeometry.centerOnScreen(dialog.getSize()).applySafe(dialog);
308 if (helpTopic != null) {
309 HelpUtil.setHelpContext(dialog.getRootPane(), helpTopic);
310 }
311 dialog.setVisible(true);
312 }
313
314 /**
315 * Displays an option dialog which is aware of a help context.
316 *
317 * @param parentComponent the parent component
318 * @param msg the message
319 * @param title the title
320 * @param messageType the message type (see {@link JOptionPane})
321 * @param helpTopic the help topic. Can be null.
322 * @return the index of the selected option or {@link JOptionPane#CLOSED_OPTION}
323 * @see #showOptionDialog(Component, Object, String, int, Icon, ButtonSpec[], ButtonSpec, String)
324 */
325 public static int showOptionDialog(Component parentComponent, Object msg, String title, int messageType, String helpTopic) {
326 return showOptionDialog(parentComponent, msg, title, messageType, null, null, null, helpTopic);
327 }
328
329 /**
330 * Run it in Event Dispatch Thread.
331 * This version does not return anything, so it is more like {@code showMessageDialog}.
332 *
333 * It can be used, when you need to show a message dialog from a worker thread,
334 * e.g. from {@code PleaseWaitRunnable}.
335 *
336 * @param parentComponent the parent component
337 * @param msg the message
338 * @param title the title
339 * @param messageType the message type (see {@link JOptionPane})
340 * @param helpTopic the help topic. Can be null.
341 */
342 public static void showMessageDialogInEDT(final Component parentComponent, final Object msg, final String title,
343 final int messageType, final String helpTopic) {
344 GuiHelper.runInEDT(() -> showOptionDialog(parentComponent, msg, title, messageType, null, null, null, helpTopic));
345 }
346}
Note: See TracBrowser for help on using the repository browser.