001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.awt.GridBagLayout; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.Map; 011import java.util.Set; 012 013import javax.swing.ButtonGroup; 014import javax.swing.JOptionPane; 015import javax.swing.JPanel; 016import javax.swing.JRadioButton; 017 018import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 019import org.openstreetmap.josm.spi.preferences.Config; 020import org.openstreetmap.josm.tools.GBC; 021import org.openstreetmap.josm.tools.Utils; 022 023/** 024 * ConditionalOptionPaneUtil provides static utility methods for displaying modal message dialogs 025 * which can be enabled/disabled by the user. 026 * 027 * They wrap the methods provided by {@link JOptionPane}. Within JOSM you should use these 028 * methods rather than the bare methods from {@link JOptionPane} because the methods provided 029 * by ConditionalOptionPaneUtil ensure that a dialog window is always on top and isn't hidden by one of the 030 * JOSM windows for detached dialogs, relation editors, history browser and the like. 031 * 032 */ 033public final class ConditionalOptionPaneUtil { 034 public static final int DIALOG_DISABLED_OPTION = Integer.MIN_VALUE; 035 036 /** (preference key => return value) mappings valid for the current operation (no, those two maps cannot be combined) */ 037 private static final Map<String, Integer> sessionChoices = new HashMap<>(); 038 /** (preference key => return value) mappings valid for the current session */ 039 private static final Map<String, Integer> immediateChoices = new HashMap<>(); 040 /** a set indication that (preference key) is or may be stored for the currently active bulk operation */ 041 private static final Set<String> immediateActive = new HashSet<>(); 042 043 /** 044 * this is a static utility class only 045 */ 046 private ConditionalOptionPaneUtil() { 047 // Hide default constructor for utility classes 048 } 049 050 /** 051 * Returns the preference value for the preference key "message." + <code>prefKey</code> + ".value". 052 * The default value if the preference key is missing is -1. 053 * 054 * @param prefKey the preference key 055 * @return the preference value for the preference key "message." + <code>prefKey</code> + ".value" 056 */ 057 public static int getDialogReturnValue(String prefKey) { 058 return Utils.firstNonNull(immediateChoices.get(prefKey), 059 sessionChoices.get(prefKey), 060 !Config.getPref().getBoolean("message." + prefKey, true) ? Config.getPref().getInt("message." + prefKey + ".value", -1) : -1 061 ); 062 } 063 064 /** 065 * Marks the beginning of a bulk operation in order to provide a "Do not show again (this operation)" option. 066 * @param prefKey the preference key 067 */ 068 public static void startBulkOperation(final String prefKey) { 069 immediateActive.add(prefKey); 070 } 071 072 /** 073 * Determines whether the key has been marked to be part of a bulk operation 074 * (in order to provide a "Do not show again (this operation)" option). 075 * @param prefKey the preference key 076 * @return {@code true} if the key has been marked to be part of a bulk operation 077 */ 078 public static boolean isInBulkOperation(final String prefKey) { 079 return immediateActive.contains(prefKey); 080 } 081 082 /** 083 * Marks the ending of a bulk operation. Removes the "Do not show again (this operation)" result value. 084 * @param prefKey the preference key 085 */ 086 public static void endBulkOperation(final String prefKey) { 087 immediateActive.remove(prefKey); 088 immediateChoices.remove(prefKey); 089 } 090 091 /** 092 * Displays an confirmation dialog with some option buttons given by <code>optionType</code>. 093 * It is always on top even if there are other open windows like detached dialogs, 094 * relation editors, history browsers and the like. 095 * 096 * Set <code>optionType</code> to {@link JOptionPane#YES_NO_OPTION} for a dialog with a YES and 097 * a NO button. 098 099 * Set <code>optionType</code> to {@link JOptionPane#YES_NO_CANCEL_OPTION} for a dialog with a YES, 100 * a NO and a CANCEL button 101 * 102 * Returns one of the constants JOptionPane.YES_OPTION, JOptionPane.NO_OPTION, 103 * JOptionPane.CANCEL_OPTION or JOptionPane.CLOSED_OPTION depending on the action chosen by 104 * the user. 105 * 106 * @param preferenceKey the preference key 107 * @param parent the parent component 108 * @param message the message 109 * @param title the title 110 * @param optionType the option type 111 * @param messageType the message type 112 * @param options a list of options 113 * @param defaultOption the default option; only meaningful if options is used; can be null 114 * 115 * @return the option selected by user. 116 * {@link JOptionPane#CLOSED_OPTION} if the dialog was closed. 117 */ 118 public static int showOptionDialog(String preferenceKey, Component parent, Object message, String title, int optionType, 119 int messageType, Object[] options, Object defaultOption) { 120 int ret = getDialogReturnValue(preferenceKey); 121 if (isYesOrNo(ret)) 122 return ret; 123 MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey)); 124 ret = JOptionPane.showOptionDialog(parent, pnl, title, optionType, messageType, null, options, defaultOption); 125 if (isYesOrNo(ret)) { 126 pnl.getNotShowAgain().store(preferenceKey, ret); 127 } 128 return ret; 129 } 130 131 /** 132 * Displays a confirmation dialog with some option buttons given by <code>optionType</code>. 133 * It is always on top even if there are other open windows like detached dialogs, 134 * relation editors, history browsers and the like. 135 * 136 * Set <code>optionType</code> to {@link JOptionPane#YES_NO_OPTION} for a dialog with a YES and 137 * a NO button. 138 139 * Set <code>optionType</code> to {@link JOptionPane#YES_NO_CANCEL_OPTION} for a dialog with a YES, 140 * a NO and a CANCEL button 141 * 142 * Replies true, if the selected option is equal to <code>trueOption</code>, otherwise false. 143 * Replies true, if the dialog is not displayed because the respective preference option 144 * <code>preferenceKey</code> is set to false and the user has previously chosen 145 * <code>trueOption</code>. 146 * 147 * @param preferenceKey the preference key 148 * @param parent the parent component 149 * @param message the message 150 * @param title the title 151 * @param optionType the option type 152 * @param messageType the message type 153 * @param trueOption if this option is selected the method replies true 154 * 155 * 156 * @return true, if the selected option is equal to <code>trueOption</code>, otherwise false. 157 * 158 * @see JOptionPane#INFORMATION_MESSAGE 159 * @see JOptionPane#WARNING_MESSAGE 160 * @see JOptionPane#ERROR_MESSAGE 161 */ 162 public static boolean showConfirmationDialog(String preferenceKey, Component parent, Object message, String title, 163 int optionType, int messageType, int trueOption) { 164 int ret = getDialogReturnValue(preferenceKey); 165 if (isYesOrNo(ret)) 166 return ret == trueOption; 167 MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey)); 168 ret = JOptionPane.showConfirmDialog(parent, pnl, title, optionType, messageType); 169 if (isYesOrNo(ret)) { 170 pnl.getNotShowAgain().store(preferenceKey, ret); 171 } 172 return ret == trueOption; 173 } 174 175 private static boolean isYesOrNo(int returnCode) { 176 return (returnCode == JOptionPane.YES_OPTION) || (returnCode == JOptionPane.NO_OPTION); 177 } 178 179 /** 180 * Displays an message in modal dialog with an OK button. Makes sure the dialog 181 * is always on top even if there are other open windows like detached dialogs, 182 * relation editors, history browsers and the like. 183 * 184 * If there is a preference with key <code>preferenceKey</code> and value <code>false</code> 185 * the dialog is not show. 186 * 187 * @param preferenceKey the preference key 188 * @param parent the parent component 189 * @param message the message 190 * @param title the title 191 * @param messageType the message type 192 * 193 * @see JOptionPane#INFORMATION_MESSAGE 194 * @see JOptionPane#WARNING_MESSAGE 195 * @see JOptionPane#ERROR_MESSAGE 196 */ 197 public static void showMessageDialog(String preferenceKey, Component parent, Object message, String title, int messageType) { 198 if (getDialogReturnValue(preferenceKey) == Integer.MAX_VALUE) 199 return; 200 MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey)); 201 JOptionPane.showMessageDialog(parent, pnl, title, messageType); 202 pnl.getNotShowAgain().store(preferenceKey, Integer.MAX_VALUE); 203 } 204 205 /** 206 * An enum designating how long to not show this message again, i.e., for how long to store 207 */ 208 enum NotShowAgain { 209 NO, OPERATION, SESSION, PERMANENT; 210 211 /** 212 * Stores the dialog result {@code value} at the corresponding place. 213 * @param prefKey the preference key 214 * @param value the dialog result 215 */ 216 void store(String prefKey, Integer value) { 217 switch (this) { 218 case NO: 219 break; 220 case OPERATION: 221 immediateChoices.put(prefKey, value); 222 break; 223 case SESSION: 224 sessionChoices.put(prefKey, value); 225 break; 226 case PERMANENT: 227 Config.getPref().putBoolean("message." + prefKey, false); 228 Config.getPref().putInt("message." + prefKey + ".value", value); 229 break; 230 } 231 } 232 233 String getLabel() { 234 switch (this) { 235 case NO: 236 return tr("Show this dialog again the next time"); 237 case OPERATION: 238 return tr("Do not show again (this operation)"); 239 case SESSION: 240 return tr("Do not show again (this session)"); 241 case PERMANENT: 242 return tr("Do not show again (remembers choice)"); 243 } 244 throw new IllegalStateException(); 245 } 246 } 247 248 /** 249 * This is a message panel used in dialogs which can be enabled/disabled with a preference setting. 250 * In addition to the normal message any {@link JOptionPane} would display it includes 251 * a checkbox for enabling/disabling this particular dialog. 252 * 253 */ 254 public static class MessagePanel extends JPanel { 255 private final JRadioButton cbShowPermanentDialog = new JRadioButton(NotShowAgain.PERMANENT.getLabel()); 256 private final JRadioButton cbShowSessionDialog = new JRadioButton(NotShowAgain.SESSION.getLabel()); 257 private final JRadioButton cbShowImmediateDialog = new JRadioButton(NotShowAgain.OPERATION.getLabel()); 258 private final JRadioButton cbStandard = new JRadioButton(NotShowAgain.NO.getLabel()); 259 260 /** 261 * Constructs a new panel. 262 * @param message the the message (null to add no message, Component instances are added directly, 263 * otherwise a JLabel with the string representation is added) 264 * @param displayImmediateOption whether to provide "Do not show again (this session)" 265 */ 266 MessagePanel(Object message, boolean displayImmediateOption) { 267 cbStandard.setSelected(true); 268 ButtonGroup group = new ButtonGroup(); 269 group.add(cbShowPermanentDialog); 270 group.add(cbShowSessionDialog); 271 group.add(cbShowImmediateDialog); 272 group.add(cbStandard); 273 274 setLayout(new GridBagLayout()); 275 if (message instanceof Component) { 276 add((Component) message, GBC.eop()); 277 } else if (message != null) { 278 add(new JMultilineLabel(message.toString()), GBC.eop()); 279 } 280 add(cbShowPermanentDialog, GBC.eol()); 281 add(cbShowSessionDialog, GBC.eol()); 282 if (displayImmediateOption) { 283 add(cbShowImmediateDialog, GBC.eol()); 284 } 285 add(cbStandard, GBC.eol()); 286 } 287 288 NotShowAgain getNotShowAgain() { 289 return cbStandard.isSelected() 290 ? NotShowAgain.NO 291 : cbShowImmediateDialog.isSelected() 292 ? NotShowAgain.OPERATION 293 : cbShowSessionDialog.isSelected() 294 ? NotShowAgain.SESSION 295 : cbShowPermanentDialog.isSelected() 296 ? NotShowAgain.PERMANENT 297 : null; 298 } 299 } 300}