| 1 | // License: GPL. For details, see LICENSE file. |
|---|
| 2 | package org.openstreetmap.josm.gui; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 5 | |
|---|
| 6 | import java.awt.Component; |
|---|
| 7 | import java.awt.Dialog.ModalityType; |
|---|
| 8 | import java.awt.event.ActionEvent; |
|---|
| 9 | import java.awt.event.KeyEvent; |
|---|
| 10 | import java.awt.event.WindowAdapter; |
|---|
| 11 | import java.awt.event.WindowEvent; |
|---|
| 12 | import java.util.ArrayList; |
|---|
| 13 | import java.util.List; |
|---|
| 14 | |
|---|
| 15 | import javax.swing.*; |
|---|
| 16 | |
|---|
| 17 | import org.openstreetmap.josm.gui.help.HelpBrowser; |
|---|
| 18 | import org.openstreetmap.josm.gui.help.HelpUtil; |
|---|
| 19 | import org.openstreetmap.josm.tools.ImageProvider; |
|---|
| 20 | import org.openstreetmap.josm.tools.InputMapUtils; |
|---|
| 21 | import org.openstreetmap.josm.tools.WindowGeometry; |
|---|
| 22 | |
|---|
| 23 | public class HelpAwareOptionPane { |
|---|
| 24 | |
|---|
| 25 | public static class ButtonSpec { |
|---|
| 26 | public String text; |
|---|
| 27 | public Icon icon; |
|---|
| 28 | public String tooltipText; |
|---|
| 29 | public String helpTopic; |
|---|
| 30 | |
|---|
| 31 | /** |
|---|
| 32 | * |
|---|
| 33 | * @param text the button text |
|---|
| 34 | * @param icon the icon to display. Can be null |
|---|
| 35 | * @param tooltipText the tooltip text. Can be null. |
|---|
| 36 | * @param helpTopic the help topic. Can be null. |
|---|
| 37 | */ |
|---|
| 38 | public ButtonSpec(String text, Icon icon, String tooltipText, String helpTopic) { |
|---|
| 39 | this.text = text; |
|---|
| 40 | this.icon = icon; |
|---|
| 41 | this.tooltipText = tooltipText; |
|---|
| 42 | this.helpTopic = helpTopic; |
|---|
| 43 | } |
|---|
| 44 | } |
|---|
| 45 | |
|---|
| 46 | static private class DefaultAction extends AbstractAction { |
|---|
| 47 | private JDialog dialog; |
|---|
| 48 | private JOptionPane pane; |
|---|
| 49 | private int value; |
|---|
| 50 | |
|---|
| 51 | public DefaultAction(JDialog dialog, JOptionPane pane, int value) { |
|---|
| 52 | this.dialog = dialog; |
|---|
| 53 | this.pane = pane; |
|---|
| 54 | this.value = value; |
|---|
| 55 | } |
|---|
| 56 | |
|---|
| 57 | public void actionPerformed(ActionEvent e) { |
|---|
| 58 | pane.setValue(value); |
|---|
| 59 | dialog.setVisible(false); |
|---|
| 60 | } |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | /** |
|---|
| 64 | * Creates the list buttons to be displayed in the option pane dialog. |
|---|
| 65 | * |
|---|
| 66 | * @param options the option. If null, just creates an OK button and a help button |
|---|
| 67 | * @param helpTopic the help topic. The context sensitive help of all buttons is equal |
|---|
| 68 | * to the context sensitive help of the whole dialog |
|---|
| 69 | * @return the list of buttons |
|---|
| 70 | */ |
|---|
| 71 | static private List<JButton> createOptionButtons(ButtonSpec[] options, String helpTopic) { |
|---|
| 72 | List<JButton> buttons = new ArrayList<JButton>(); |
|---|
| 73 | if (options == null) { |
|---|
| 74 | JButton b = new JButton(tr("OK")); |
|---|
| 75 | b.setIcon(ImageProvider.get("ok")); |
|---|
| 76 | b.setToolTipText(tr("Click to close the dialog")); |
|---|
| 77 | b.setFocusable(true); |
|---|
| 78 | buttons.add(b); |
|---|
| 79 | } else { |
|---|
| 80 | for (ButtonSpec spec: options) { |
|---|
| 81 | JButton b = new JButton(spec.text); |
|---|
| 82 | b.setIcon(spec.icon); |
|---|
| 83 | b.setToolTipText(spec.tooltipText == null? "" : spec.tooltipText); |
|---|
| 84 | if (helpTopic != null) { |
|---|
| 85 | HelpUtil.setHelpContext(b, helpTopic); |
|---|
| 86 | } |
|---|
| 87 | b.setFocusable(true); |
|---|
| 88 | buttons.add(b); |
|---|
| 89 | } |
|---|
| 90 | } |
|---|
| 91 | return buttons; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | /** |
|---|
| 95 | * Creates the help button |
|---|
| 96 | * |
|---|
| 97 | * @param helpTopic the help topic |
|---|
| 98 | * @return the help button |
|---|
| 99 | */ |
|---|
| 100 | static private JButton createHelpButton(final String helpTopic) { |
|---|
| 101 | JButton b = new JButton(tr("Help")); |
|---|
| 102 | b.setIcon(ImageProvider.get("help")); |
|---|
| 103 | b.setToolTipText(tr("Show help information")); |
|---|
| 104 | HelpUtil.setHelpContext(b, helpTopic); |
|---|
| 105 | Action a = new AbstractAction() { |
|---|
| 106 | public void actionPerformed(ActionEvent e) { |
|---|
| 107 | HelpBrowser.setUrlForHelpTopic(helpTopic); |
|---|
| 108 | } |
|---|
| 109 | }; |
|---|
| 110 | b.addActionListener(a); |
|---|
| 111 | InputMapUtils.enableEnter(b); |
|---|
| 112 | return b; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | /** |
|---|
| 116 | * Displays an option dialog which is aware of a help context. If <code>helpTopic</code> isn't null, |
|---|
| 117 | * the dialog includes a "Help" button and launches the help browser if the user presses F1. If the |
|---|
| 118 | * user clicks on the "Help" button the option dialog remains open and JOSM launches the help |
|---|
| 119 | * browser. |
|---|
| 120 | * |
|---|
| 121 | * <code>helpTopic</code> is the trailing part of a JOSM online help URL, i.e. the part after the leading |
|---|
| 122 | * <code>http://josm.openstreetmap.de/wiki/Help</code>. It should start with a leading '/' and it |
|---|
| 123 | * may include an anchor after a '#'. |
|---|
| 124 | * |
|---|
| 125 | * <strong>Examples</strong> |
|---|
| 126 | * <ul> |
|---|
| 127 | * <li>/Dialogs/RelationEditor</li> |
|---|
| 128 | * <li>/Dialogs/RelationEditor#ConflictInData</li> |
|---|
| 129 | * </ul> |
|---|
| 130 | * |
|---|
| 131 | * In addition, the option buttons display JOSM icons, similar to ExtendedDialog. |
|---|
| 132 | * |
|---|
| 133 | * @param parentComponent the parent component |
|---|
| 134 | * @param msg the message |
|---|
| 135 | * @param title the title |
|---|
| 136 | * @param messageType the message type (see {@see JOptionPane}) |
|---|
| 137 | * @param icon the icon to display. Can be null. |
|---|
| 138 | * @param options the list of options to display. Can be null. |
|---|
| 139 | * @param defaultOption the default option. Can be null. |
|---|
| 140 | * @param helpTopic the help topic. Can be null. |
|---|
| 141 | * @return the index of the selected option or {@link JOptionPane#CLOSED_OPTION} |
|---|
| 142 | */ |
|---|
| 143 | static public int showOptionDialog(Component parentComponent, Object msg, String title, int messageType, Icon icon, final ButtonSpec[] options, final ButtonSpec defaultOption, final String helpTopic) { |
|---|
| 144 | final List<JButton> buttons = createOptionButtons(options, helpTopic); |
|---|
| 145 | if (helpTopic != null) { |
|---|
| 146 | buttons.add(createHelpButton(helpTopic)); |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | JButton defaultButton = null; |
|---|
| 150 | if (options != null && defaultOption != null) { |
|---|
| 151 | for (int i=0; i< options.length; i++) { |
|---|
| 152 | if (options[i] == defaultOption) { |
|---|
| 153 | defaultButton = buttons.get(i); |
|---|
| 154 | break; |
|---|
| 155 | } |
|---|
| 156 | } |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | if (msg instanceof String) { |
|---|
| 160 | JEditorPane pane = new JEditorPane(); |
|---|
| 161 | pane.setContentType("text/html"); |
|---|
| 162 | pane.setText((String) msg); |
|---|
| 163 | pane.setEditable(false); |
|---|
| 164 | pane.setOpaque(false); |
|---|
| 165 | msg = pane; |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | final JOptionPane pane = new JOptionPane( |
|---|
| 169 | msg, |
|---|
| 170 | messageType, |
|---|
| 171 | JOptionPane.DEFAULT_OPTION, |
|---|
| 172 | icon, |
|---|
| 173 | buttons.toArray(), |
|---|
| 174 | defaultButton |
|---|
| 175 | ); |
|---|
| 176 | |
|---|
| 177 | pane.getValue(); |
|---|
| 178 | final JDialog dialog = new JDialog( |
|---|
| 179 | JOptionPane.getFrameForComponent(parentComponent), |
|---|
| 180 | title, |
|---|
| 181 | ModalityType.DOCUMENT_MODAL |
|---|
| 182 | ); |
|---|
| 183 | dialog.setContentPane(pane); |
|---|
| 184 | dialog.addWindowListener(new WindowAdapter() { |
|---|
| 185 | @Override |
|---|
| 186 | public void windowClosing(WindowEvent e) { |
|---|
| 187 | pane.setValue(JOptionPane.CLOSED_OPTION); |
|---|
| 188 | super.windowClosed(e); |
|---|
| 189 | } |
|---|
| 190 | |
|---|
| 191 | @Override |
|---|
| 192 | public void windowOpened(WindowEvent e) { |
|---|
| 193 | if (defaultOption != null && options != null && options.length > 0) { |
|---|
| 194 | int i; |
|---|
| 195 | for (i=0; i<options.length;i++) { |
|---|
| 196 | if (options[i] == defaultOption) { |
|---|
| 197 | break; |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | if (i >= options.length) { |
|---|
| 201 | buttons.get(0).requestFocusInWindow(); |
|---|
| 202 | } |
|---|
| 203 | buttons.get(i).requestFocusInWindow(); |
|---|
| 204 | } else { |
|---|
| 205 | buttons.get(0).requestFocusInWindow(); |
|---|
| 206 | } |
|---|
| 207 | } |
|---|
| 208 | }); |
|---|
| 209 | dialog.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "close"); |
|---|
| 210 | dialog.getRootPane().getActionMap().put("close", new AbstractAction() { |
|---|
| 211 | public void actionPerformed(ActionEvent e) { |
|---|
| 212 | pane.setValue(JOptionPane.CLOSED_OPTION); |
|---|
| 213 | dialog.setVisible(false); |
|---|
| 214 | }} |
|---|
| 215 | ); |
|---|
| 216 | |
|---|
| 217 | if (options != null) { |
|---|
| 218 | for (int i=0; i < options.length;i++) { |
|---|
| 219 | final DefaultAction action = new DefaultAction(dialog, pane, i); |
|---|
| 220 | buttons.get(i).addActionListener(action); |
|---|
| 221 | buttons.get(i).getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); |
|---|
| 222 | buttons.get(i).getActionMap().put("enter", action); |
|---|
| 223 | } |
|---|
| 224 | } else { |
|---|
| 225 | final DefaultAction action = new DefaultAction(dialog, pane, 0); |
|---|
| 226 | buttons.get(0).addActionListener(action); |
|---|
| 227 | buttons.get(0).getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); |
|---|
| 228 | buttons.get(0).getActionMap().put("enter", action); |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | dialog.pack(); |
|---|
| 232 | WindowGeometry.centerOnScreen(dialog.getSize()).applySafe(dialog); |
|---|
| 233 | if (helpTopic != null) { |
|---|
| 234 | HelpUtil.setHelpContext(dialog.getRootPane(), helpTopic); |
|---|
| 235 | } |
|---|
| 236 | dialog.setVisible(true); |
|---|
| 237 | return (Integer)pane.getValue(); |
|---|
| 238 | } |
|---|
| 239 | |
|---|
| 240 | /** |
|---|
| 241 | * |
|---|
| 242 | * @param parentComponent |
|---|
| 243 | * @param msg |
|---|
| 244 | * @param title |
|---|
| 245 | * @param messageType |
|---|
| 246 | * @param helpTopic |
|---|
| 247 | * @return |
|---|
| 248 | * @see #showOptionDialog(Component, Object, String, int, Icon, ButtonSpec[], ButtonSpec, String) |
|---|
| 249 | */ |
|---|
| 250 | static public int showOptionDialog(Component parentComponent, Object msg, String title, int messageType,final String helpTopic) { |
|---|
| 251 | return showOptionDialog(parentComponent, msg, title, messageType, null,null,null, helpTopic); |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | /** |
|---|
| 255 | * Run it in Event Dispatch Thread. |
|---|
| 256 | * This version does not return anything, so it is more like showMessageDialog. |
|---|
| 257 | * |
|---|
| 258 | * It can be used, when you need to show a message dialog from a worker thread, |
|---|
| 259 | * e.g. from PleaseWaitRunnable |
|---|
| 260 | */ |
|---|
| 261 | static public void showMessageDialogInEDT(final Component parentComponent, final Object msg, final String title, final int messageType, final String helpTopic) { |
|---|
| 262 | SwingUtilities.invokeLater(new Runnable() { |
|---|
| 263 | public void run() { |
|---|
| 264 | showOptionDialog(parentComponent, msg, title, messageType, null, null, null, helpTopic); |
|---|
| 265 | } |
|---|
| 266 | }); |
|---|
| 267 | } |
|---|
| 268 | } |
|---|