001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.Component; 007 import java.awt.Dialog.ModalityType; 008 import java.awt.event.ActionEvent; 009 import java.awt.event.KeyEvent; 010 import java.awt.event.WindowAdapter; 011 import java.awt.event.WindowEvent; 012 import java.util.ArrayList; 013 import java.util.List; 014 015 import javax.swing.*; 016 017 import org.openstreetmap.josm.gui.help.HelpBrowser; 018 import org.openstreetmap.josm.gui.help.HelpUtil; 019 import org.openstreetmap.josm.tools.ImageProvider; 020 import org.openstreetmap.josm.tools.InputMapUtils; 021 import org.openstreetmap.josm.tools.WindowGeometry; 022 023 public class HelpAwareOptionPane { 024 025 public static class ButtonSpec { 026 public String text; 027 public Icon icon; 028 public String tooltipText; 029 public String helpTopic; 030 031 /** 032 * 033 * @param text the button text 034 * @param icon the icon to display. Can be null 035 * @param tooltipText the tooltip text. Can be null. 036 * @param helpTopic the help topic. Can be null. 037 */ 038 public ButtonSpec(String text, Icon icon, String tooltipText, String helpTopic) { 039 this.text = text; 040 this.icon = icon; 041 this.tooltipText = tooltipText; 042 this.helpTopic = helpTopic; 043 } 044 } 045 046 static private class DefaultAction extends AbstractAction { 047 private JDialog dialog; 048 private JOptionPane pane; 049 private int value; 050 051 public DefaultAction(JDialog dialog, JOptionPane pane, int value) { 052 this.dialog = dialog; 053 this.pane = pane; 054 this.value = value; 055 } 056 057 public void actionPerformed(ActionEvent e) { 058 pane.setValue(value); 059 dialog.setVisible(false); 060 } 061 } 062 063 /** 064 * Creates the list buttons to be displayed in the option pane dialog. 065 * 066 * @param options the option. If null, just creates an OK button and a help button 067 * @param helpTopic the help topic. The context sensitive help of all buttons is equal 068 * to the context sensitive help of the whole dialog 069 * @return the list of buttons 070 */ 071 static private List<JButton> createOptionButtons(ButtonSpec[] options, String helpTopic) { 072 List<JButton> buttons = new ArrayList<JButton>(); 073 if (options == null) { 074 JButton b = new JButton(tr("OK")); 075 b.setIcon(ImageProvider.get("ok")); 076 b.setToolTipText(tr("Click to close the dialog")); 077 b.setFocusable(true); 078 buttons.add(b); 079 } else { 080 for (ButtonSpec spec: options) { 081 JButton b = new JButton(spec.text); 082 b.setIcon(spec.icon); 083 b.setToolTipText(spec.tooltipText == null? "" : spec.tooltipText); 084 if (helpTopic != null) { 085 HelpUtil.setHelpContext(b, helpTopic); 086 } 087 b.setFocusable(true); 088 buttons.add(b); 089 } 090 } 091 return buttons; 092 } 093 094 /** 095 * Creates the help button 096 * 097 * @param helpTopic the help topic 098 * @return the help button 099 */ 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 {@link 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 }