[6380] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[1838] | 2 | package org.openstreetmap.josm.gui;
|
---|
[626] | 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[1838] | 6 | import java.awt.Component;
|
---|
[9496] | 7 | import java.awt.GraphicsEnvironment;
|
---|
[626] | 8 | import java.awt.GridBagLayout;
|
---|
[6594] | 9 | import java.util.HashMap;
|
---|
| 10 | import java.util.HashSet;
|
---|
| 11 | import java.util.Map;
|
---|
| 12 | import java.util.Set;
|
---|
[626] | 13 |
|
---|
[6594] | 14 | import javax.swing.ButtonGroup;
|
---|
[626] | 15 | import javax.swing.JOptionPane;
|
---|
| 16 | import javax.swing.JPanel;
|
---|
[6594] | 17 | import javax.swing.JRadioButton;
|
---|
[626] | 18 |
|
---|
| 19 | import org.openstreetmap.josm.Main;
|
---|
[6901] | 20 | import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
|
---|
[1838] | 21 | import org.openstreetmap.josm.tools.GBC;
|
---|
[6594] | 22 | import org.openstreetmap.josm.tools.Utils;
|
---|
[626] | 23 |
|
---|
[1838] | 24 | /**
|
---|
| 25 | * ConditionalOptionPaneUtil provides static utility methods for displaying modal message dialogs
|
---|
| 26 | * which can be enabled/disabled by the user.
|
---|
[2512] | 27 | *
|
---|
[5266] | 28 | * They wrap the methods provided by {@link JOptionPane}. Within JOSM you should use these
|
---|
| 29 | * methods rather than the bare methods from {@link JOptionPane} because the methods provided
|
---|
[1838] | 30 | * by ConditionalOptionPaneUtil ensure that a dialog window is always on top and isn't hidden by one of the
|
---|
| 31 | * JOSM windows for detached dialogs, relation editors, history browser and the like.
|
---|
[2512] | 32 | *
|
---|
[1838] | 33 | */
|
---|
[6362] | 34 | public final class ConditionalOptionPaneUtil {
|
---|
[6623] | 35 | public static final int DIALOG_DISABLED_OPTION = Integer.MIN_VALUE;
|
---|
[1838] | 36 |
|
---|
[6830] | 37 | /** (preference key => return value) mappings valid for the current operation (no, those two maps cannot be combined) */
|
---|
[8468] | 38 | private static final Map<String, Integer> sessionChoices = new HashMap<>();
|
---|
[6830] | 39 | /** (preference key => return value) mappings valid for the current session */
|
---|
[8468] | 40 | private static final Map<String, Integer> immediateChoices = new HashMap<>();
|
---|
[6594] | 41 | /** a set indication that (preference key) is or may be stored for the currently active bulk operation */
|
---|
[8468] | 42 | private static final Set<String> immediateActive = new HashSet<>();
|
---|
[6594] | 43 |
|
---|
[1838] | 44 | /**
|
---|
| 45 | * this is a static utility class only
|
---|
| 46 | */
|
---|
| 47 | private ConditionalOptionPaneUtil() {}
|
---|
| 48 |
|
---|
| 49 | /**
|
---|
[6594] | 50 | * Returns the preference value for the preference key "message." + <code>prefKey</code> + ".value".
|
---|
| 51 | * The default value if the preference key is missing is -1.
|
---|
[2512] | 52 | *
|
---|
[5817] | 53 | * @param prefKey the preference key
|
---|
[6594] | 54 | * @return the preference value for the preference key "message." + <code>prefKey</code> + ".value"
|
---|
[1838] | 55 | */
|
---|
[6594] | 56 | public static int getDialogReturnValue(String prefKey) {
|
---|
| 57 | return Utils.firstNonNull(
|
---|
| 58 | immediateChoices.get(prefKey),
|
---|
| 59 | sessionChoices.get(prefKey),
|
---|
| 60 | !Main.pref.getBoolean("message." + prefKey, true) ? Main.pref.getInteger("message." + prefKey + ".value", -1) : -1
|
---|
| 61 | );
|
---|
[1169] | 62 | }
|
---|
[626] | 63 |
|
---|
[1838] | 64 | /**
|
---|
[6594] | 65 | * Marks the beginning of a bulk operation in order to provide a "Do not show again (this operation)" option.
|
---|
| 66 | * @param prefKey the preference key
|
---|
[1838] | 67 | */
|
---|
[6594] | 68 | public static void startBulkOperation(final String prefKey) {
|
---|
| 69 | immediateActive.add(prefKey);
|
---|
[1169] | 70 | }
|
---|
[745] | 71 |
|
---|
[1841] | 72 | /**
|
---|
[8540] | 73 | * Determines whether the key has been marked to be part of a bulk operation
|
---|
| 74 | * (in order to provide a "Do not show again (this operation)" option).
|
---|
[6595] | 75 | * @param prefKey the preference key
|
---|
[8958] | 76 | * @return {@code true} if the key has been marked to be part of a bulk operation
|
---|
[6595] | 77 | */
|
---|
| 78 | public static boolean isInBulkOperation(final String prefKey) {
|
---|
| 79 | return immediateActive.contains(prefKey);
|
---|
| 80 | }
|
---|
| 81 |
|
---|
| 82 | /**
|
---|
[6594] | 83 | * Marks the ending of a bulk operation. Removes the "Do not show again (this operation)" result value.
|
---|
| 84 | * @param prefKey the preference key
|
---|
[2522] | 85 | */
|
---|
[6594] | 86 | public static void endBulkOperation(final String prefKey) {
|
---|
| 87 | immediateActive.remove(prefKey);
|
---|
| 88 | immediateChoices.remove(prefKey);
|
---|
[2522] | 89 | }
|
---|
| 90 |
|
---|
| 91 | /**
|
---|
[1841] | 92 | * Displays an confirmation dialog with some option buttons given by <code>optionType</code>.
|
---|
| 93 | * It is always on top even if there are other open windows like detached dialogs,
|
---|
| 94 | * relation editors, history browsers and the like.
|
---|
[2512] | 95 | *
|
---|
[5266] | 96 | * Set <code>optionType</code> to {@link JOptionPane#YES_NO_OPTION} for a dialog with a YES and
|
---|
[1841] | 97 | * a NO button.
|
---|
[1838] | 98 |
|
---|
[5266] | 99 | * Set <code>optionType</code> to {@link JOptionPane#YES_NO_CANCEL_OPTION} for a dialog with a YES,
|
---|
[1841] | 100 | * a NO and a CANCEL button
|
---|
[2512] | 101 | *
|
---|
[2929] | 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.
|
---|
[2512] | 105 | *
|
---|
[1841] | 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
|
---|
[5724] | 113 | * @param defaultOption the default option; only meaningful if options is used; can be null
|
---|
[2512] | 114 | *
|
---|
[9496] | 115 | * @return the option selected by user.
|
---|
| 116 | * {@link JOptionPane#CLOSED_OPTION} if the dialog was closed.
|
---|
| 117 | * {@link JOptionPane#YES_OPTION} if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
|
---|
[1841] | 118 | */
|
---|
[8443] | 119 | public static int showOptionDialog(String preferenceKey, Component parent, Object message, String title, int optionType,
|
---|
[9496] | 120 | int messageType, Object[] options, Object defaultOption) {
|
---|
[2929] | 121 | int ret = getDialogReturnValue(preferenceKey);
|
---|
[6594] | 122 | if (isYesOrNo(ret))
|
---|
[2929] | 123 | return ret;
|
---|
[6595] | 124 | MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
|
---|
[9496] | 125 | if (GraphicsEnvironment.isHeadless()) {
|
---|
| 126 | // for unit tests
|
---|
| 127 | ret = JOptionPane.YES_OPTION;
|
---|
| 128 | } else {
|
---|
| 129 | ret = JOptionPane.showOptionDialog(parent, pnl, title, optionType, messageType, null, options, defaultOption);
|
---|
| 130 | }
|
---|
[6594] | 131 | if (isYesOrNo(ret)) {
|
---|
| 132 | pnl.getNotShowAgain().store(preferenceKey, ret);
|
---|
[2929] | 133 | }
|
---|
[1841] | 134 | return ret;
|
---|
| 135 | }
|
---|
| 136 |
|
---|
[1838] | 137 | /**
|
---|
[2929] | 138 | * Displays a confirmation dialog with some option buttons given by <code>optionType</code>.
|
---|
[1838] | 139 | * It is always on top even if there are other open windows like detached dialogs,
|
---|
| 140 | * relation editors, history browsers and the like.
|
---|
[2512] | 141 | *
|
---|
[5266] | 142 | * Set <code>optionType</code> to {@link JOptionPane#YES_NO_OPTION} for a dialog with a YES and
|
---|
[1838] | 143 | * a NO button.
|
---|
| 144 |
|
---|
[5266] | 145 | * Set <code>optionType</code> to {@link JOptionPane#YES_NO_CANCEL_OPTION} for a dialog with a YES,
|
---|
[1838] | 146 | * a NO and a CANCEL button
|
---|
[2512] | 147 | *
|
---|
[1838] | 148 | * Replies true, if the selected option is equal to <code>trueOption</code>, otherwise false.
|
---|
| 149 | * Replies true, if the dialog is not displayed because the respective preference option
|
---|
[2929] | 150 | * <code>preferenceKey</code> is set to false and the user has previously chosen
|
---|
| 151 | * <code>trueOption</code>.
|
---|
[2512] | 152 | *
|
---|
[1838] | 153 | * @param preferenceKey the preference key
|
---|
| 154 | * @param parent the parent component
|
---|
| 155 | * @param message the message
|
---|
| 156 | * @param title the title
|
---|
| 157 | * @param optionType the option type
|
---|
| 158 | * @param messageType the message type
|
---|
[9496] | 159 | * @param trueOption if this option is selected the method replies true
|
---|
[2512] | 160 | *
|
---|
| 161 | *
|
---|
[1838] | 162 | * @return true, if the selected option is equal to <code>trueOption</code>, otherwise false.
|
---|
[9496] | 163 | * {@code trueOption} if <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
|
---|
[2512] | 164 | *
|
---|
[1838] | 165 | * @see JOptionPane#INFORMATION_MESSAGE
|
---|
| 166 | * @see JOptionPane#WARNING_MESSAGE
|
---|
| 167 | * @see JOptionPane#ERROR_MESSAGE
|
---|
| 168 | */
|
---|
[8509] | 169 | public static boolean showConfirmationDialog(String preferenceKey, Component parent, Object message, String title,
|
---|
[9496] | 170 | int optionType, int messageType, int trueOption) {
|
---|
[2929] | 171 | int ret = getDialogReturnValue(preferenceKey);
|
---|
[6594] | 172 | if (isYesOrNo(ret))
|
---|
[2929] | 173 | return ret == trueOption;
|
---|
[6595] | 174 | MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
|
---|
[9496] | 175 | if (GraphicsEnvironment.isHeadless()) {
|
---|
| 176 | // for unit tests
|
---|
| 177 | ret = trueOption;
|
---|
| 178 | } else {
|
---|
| 179 | ret = JOptionPane.showConfirmDialog(parent, pnl, title, optionType, messageType);
|
---|
| 180 | }
|
---|
[8345] | 181 | if (isYesOrNo(ret)) {
|
---|
[6594] | 182 | pnl.getNotShowAgain().store(preferenceKey, ret);
|
---|
[2522] | 183 | }
|
---|
[2017] | 184 | return ret == trueOption;
|
---|
[1169] | 185 | }
|
---|
[745] | 186 |
|
---|
[6594] | 187 | private static boolean isYesOrNo(int returnCode) {
|
---|
| 188 | return (returnCode == JOptionPane.YES_OPTION) || (returnCode == JOptionPane.NO_OPTION);
|
---|
| 189 | }
|
---|
| 190 |
|
---|
[1838] | 191 | /**
|
---|
| 192 | * Displays an message in modal dialog with an OK button. Makes sure the dialog
|
---|
| 193 | * is always on top even if there are other open windows like detached dialogs,
|
---|
| 194 | * relation editors, history browsers and the like.
|
---|
[2512] | 195 | *
|
---|
[1838] | 196 | * If there is a preference with key <code>preferenceKey</code> and value <code>false</code>
|
---|
| 197 | * the dialog is not show.
|
---|
[2512] | 198 | *
|
---|
[1838] | 199 | * @param preferenceKey the preference key
|
---|
| 200 | * @param parent the parent component
|
---|
| 201 | * @param message the message
|
---|
| 202 | * @param title the title
|
---|
| 203 | * @param messageType the message type
|
---|
[2512] | 204 | *
|
---|
[1838] | 205 | * @see JOptionPane#INFORMATION_MESSAGE
|
---|
| 206 | * @see JOptionPane#WARNING_MESSAGE
|
---|
| 207 | * @see JOptionPane#ERROR_MESSAGE
|
---|
| 208 | */
|
---|
[8510] | 209 | public static void showMessageDialog(String preferenceKey, Component parent, Object message, String title, int messageType) {
|
---|
[6594] | 210 | if (getDialogReturnValue(preferenceKey) == Integer.MAX_VALUE)
|
---|
[1838] | 211 | return;
|
---|
[6595] | 212 | MessagePanel pnl = new MessagePanel(message, isInBulkOperation(preferenceKey));
|
---|
[2017] | 213 | JOptionPane.showMessageDialog(parent, pnl, title, messageType);
|
---|
[6594] | 214 | pnl.getNotShowAgain().store(preferenceKey, Integer.MAX_VALUE);
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | /**
|
---|
| 218 | * An enum designating how long to not show this message again, i.e., for how long to store
|
---|
| 219 | */
|
---|
[8836] | 220 | enum NotShowAgain {
|
---|
[6594] | 221 | NO, OPERATION, SESSION, PERMANENT;
|
---|
| 222 |
|
---|
| 223 | /**
|
---|
| 224 | * Stores the dialog result {@code value} at the corresponding place.
|
---|
| 225 | * @param prefKey the preference key
|
---|
| 226 | * @param value the dialog result
|
---|
| 227 | */
|
---|
| 228 | void store(String prefKey, Integer value) {
|
---|
| 229 | switch (this) {
|
---|
| 230 | case NO:
|
---|
| 231 | break;
|
---|
| 232 | case OPERATION:
|
---|
| 233 | immediateChoices.put(prefKey, value);
|
---|
| 234 | break;
|
---|
| 235 | case SESSION:
|
---|
| 236 | sessionChoices.put(prefKey, value);
|
---|
| 237 | break;
|
---|
| 238 | case PERMANENT:
|
---|
| 239 | Main.pref.put("message." + prefKey, false);
|
---|
| 240 | Main.pref.putInteger("message." + prefKey + ".value", value);
|
---|
| 241 | break;
|
---|
| 242 | }
|
---|
[2929] | 243 | }
|
---|
[6594] | 244 |
|
---|
| 245 | String getLabel() {
|
---|
| 246 | switch (this) {
|
---|
| 247 | case NO:
|
---|
| 248 | return tr("Show this dialog again the next time");
|
---|
| 249 | case OPERATION:
|
---|
| 250 | return tr("Do not show again (this operation)");
|
---|
| 251 | case SESSION:
|
---|
| 252 | return tr("Do not show again (this session)");
|
---|
| 253 | case PERMANENT:
|
---|
| 254 | return tr("Do not show again (remembers choice)");
|
---|
| 255 | }
|
---|
| 256 | throw new IllegalStateException();
|
---|
| 257 | }
|
---|
[1838] | 258 | }
|
---|
| 259 |
|
---|
| 260 | /**
|
---|
[8468] | 261 | * This is a message panel used in dialogs which can be enabled/disabled with a preference setting.
|
---|
[5266] | 262 | * In addition to the normal message any {@link JOptionPane} would display it includes
|
---|
[1838] | 263 | * a checkbox for enabling/disabling this particular dialog.
|
---|
| 264 | *
|
---|
| 265 | */
|
---|
[6594] | 266 | static class MessagePanel extends JPanel {
|
---|
| 267 | private final JRadioButton cbShowPermanentDialog = new JRadioButton(NotShowAgain.PERMANENT.getLabel());
|
---|
| 268 | private final JRadioButton cbShowSessionDialog = new JRadioButton(NotShowAgain.SESSION.getLabel());
|
---|
| 269 | private final JRadioButton cbShowImmediateDialog = new JRadioButton(NotShowAgain.OPERATION.getLabel());
|
---|
| 270 | private final JRadioButton cbStandard = new JRadioButton(NotShowAgain.NO.getLabel());
|
---|
[1838] | 271 |
|
---|
[6594] | 272 | /**
|
---|
| 273 | * Constructs a new panel.
|
---|
[8468] | 274 | * @param message the the message (null to add no message, Component instances are added directly,
|
---|
| 275 | * otherwise a JLabel with the string representation is added)
|
---|
[6594] | 276 | * @param displayImmediateOption whether to provide "Do not show again (this session)"
|
---|
| 277 | */
|
---|
[8836] | 278 | MessagePanel(Object message, boolean displayImmediateOption) {
|
---|
[6594] | 279 | cbStandard.setSelected(true);
|
---|
[8468] | 280 | ButtonGroup group = new ButtonGroup();
|
---|
[6594] | 281 | group.add(cbShowPermanentDialog);
|
---|
| 282 | group.add(cbShowSessionDialog);
|
---|
| 283 | group.add(cbShowImmediateDialog);
|
---|
| 284 | group.add(cbStandard);
|
---|
| 285 |
|
---|
[1838] | 286 | setLayout(new GridBagLayout());
|
---|
| 287 | if (message instanceof Component) {
|
---|
[6594] | 288 | add((Component) message, GBC.eop());
|
---|
| 289 | } else if (message != null) {
|
---|
[6901] | 290 | add(new JMultilineLabel(message.toString()), GBC.eop());
|
---|
[1838] | 291 | }
|
---|
[6594] | 292 | add(cbShowPermanentDialog, GBC.eol());
|
---|
| 293 | add(cbShowSessionDialog, GBC.eol());
|
---|
| 294 | if (displayImmediateOption) {
|
---|
| 295 | add(cbShowImmediateDialog, GBC.eol());
|
---|
| 296 | }
|
---|
| 297 | add(cbStandard, GBC.eol());
|
---|
[1169] | 298 | }
|
---|
[1838] | 299 |
|
---|
[6594] | 300 | NotShowAgain getNotShowAgain() {
|
---|
| 301 | return cbStandard.isSelected()
|
---|
| 302 | ? NotShowAgain.NO
|
---|
| 303 | : cbShowImmediateDialog.isSelected()
|
---|
| 304 | ? NotShowAgain.OPERATION
|
---|
| 305 | : cbShowSessionDialog.isSelected()
|
---|
| 306 | ? NotShowAgain.SESSION
|
---|
| 307 | : cbShowPermanentDialog.isSelected()
|
---|
| 308 | ? NotShowAgain.PERMANENT
|
---|
| 309 | : null;
|
---|
[1838] | 310 | }
|
---|
[1169] | 311 | }
|
---|
[626] | 312 | }
|
---|