source: josm/trunk/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java @ 11921

Last change on this file since 11921 was 11921, checked in by Don-vip, 6 months ago

improve unit test coverage of utilities classes thanks to https://trajano.github.io/commons-testing

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