source: josm/trunk/src/org/openstreetmap/josm/gui/ExtendedDialog.java@ 12496

Last change on this file since 12496 was 11945, checked in by Don-vip, 7 years ago

sonar - fb-contrib:SCII_SPOILED_CHILD_INTERFACE_IMPLEMENTOR - Style - Class implements interface by relying on unknowing superclass methods

  • Property svn:eol-style set to native
File size: 19.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.Dimension;
8import java.awt.Frame;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.Insets;
12import java.awt.event.ActionEvent;
13import java.awt.event.KeyEvent;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collections;
17import java.util.HashSet;
18import java.util.List;
19import java.util.Set;
20
21import javax.swing.AbstractAction;
22import javax.swing.Action;
23import javax.swing.Icon;
24import javax.swing.JButton;
25import javax.swing.JDialog;
26import javax.swing.JLabel;
27import javax.swing.JOptionPane;
28import javax.swing.JPanel;
29import javax.swing.JScrollBar;
30import javax.swing.JScrollPane;
31import javax.swing.KeyStroke;
32import javax.swing.UIManager;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.gui.help.HelpBrowser;
36import org.openstreetmap.josm.gui.help.HelpUtil;
37import org.openstreetmap.josm.gui.util.GuiHelper;
38import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
39import org.openstreetmap.josm.io.OnlineResource;
40import org.openstreetmap.josm.tools.GBC;
41import org.openstreetmap.josm.tools.ImageProvider;
42import org.openstreetmap.josm.tools.InputMapUtils;
43import org.openstreetmap.josm.tools.Utils;
44import org.openstreetmap.josm.tools.WindowGeometry;
45
46/**
47 * General configurable dialog window.
48 *
49 * If dialog is modal, you can use {@link #getValue()} to retrieve the
50 * button index. Note that the user can close the dialog
51 * by other means. This is usually equivalent to cancel action.
52 *
53 * For non-modal dialogs, {@link #buttonAction(int, ActionEvent)} can be overridden.
54 *
55 * There are various options, see below.
56 *
57 * Note: The button indices are counted from 1 and upwards.
58 * So for {@link #getValue()}, {@link #setDefaultButton(int)} and
59 * {@link #setCancelButton} the first button has index 1.
60 *
61 * Simple example:
62 * <pre>
63 * ExtendedDialog ed = new ExtendedDialog(
64 * Main.parent, tr("Dialog Title"),
65 * new String[] {tr("Ok"), tr("Cancel")});
66 * ed.setButtonIcons(new String[] {"ok", "cancel"}); // optional
67 * ed.setIcon(JOptionPane.WARNING_MESSAGE); // optional
68 * ed.setContent(tr("Really proceed? Interesting things may happen..."));
69 * ed.showDialog();
70 * if (ed.getValue() == 1) { // user clicked first button "Ok"
71 * // proceed...
72 * }
73 * </pre>
74 */
75public class ExtendedDialog extends JDialog implements IExtendedDialog {
76 private final boolean disposeOnClose;
77 private volatile int result;
78 public static final int DialogClosedOtherwise = 0;
79 private boolean toggleable;
80 private String rememberSizePref = "";
81 private transient WindowGeometry defaultWindowGeometry;
82 private String togglePref = "";
83 private int toggleValue = -1;
84 private ConditionalOptionPaneUtil.MessagePanel togglePanel;
85 private Component parent;
86 private Component content;
87 private final String[] bTexts;
88 private String[] bToolTipTexts;
89 private transient Icon[] bIcons;
90 private Set<Integer> cancelButtonIdx = Collections.emptySet();
91 private int defaultButtonIdx = 1;
92 protected JButton defaultButton;
93 private transient Icon icon;
94 private boolean modal;
95 private boolean focusOnDefaultButton;
96
97 /** true, if the dialog should include a help button */
98 private boolean showHelpButton;
99 /** the help topic */
100 private String helpTopic;
101
102 /**
103 * set to true if the content of the extended dialog should
104 * be placed in a {@link JScrollPane}
105 */
106 private boolean placeContentInScrollPane;
107
108 // For easy access when inherited
109 protected transient Insets contentInsets = new Insets(10, 5, 0, 5);
110 protected transient List<JButton> buttons = new ArrayList<>();
111
112 /**
113 * This method sets up the most basic options for the dialog. Add more
114 * advanced features with dedicated methods.
115 * Possible features:
116 * <ul>
117 * <li><code>setButtonIcons</code></li>
118 * <li><code>setContent</code></li>
119 * <li><code>toggleEnable</code></li>
120 * <li><code>toggleDisable</code></li>
121 * <li><code>setToggleCheckboxText</code></li>
122 * <li><code>setRememberWindowGeometry</code></li>
123 * </ul>
124 *
125 * When done, call <code>showDialog</code> to display it. You can receive
126 * the user's choice using <code>getValue</code>. Have a look at this function
127 * for possible return values.
128 *
129 * @param parent The parent element that will be used for position and maximum size
130 * @param title The text that will be shown in the window titlebar
131 * @param buttonTexts String Array of the text that will appear on the buttons. The first button is the default one.
132 */
133 public ExtendedDialog(Component parent, String title, String... buttonTexts) {
134 this(parent, title, buttonTexts, true, true);
135 }
136
137 /**
138 * Same as above but lets you define if the dialog should be modal.
139 * @param parent The parent element that will be used for position and maximum size
140 * @param title The text that will be shown in the window titlebar
141 * @param buttonTexts String Array of the text that will appear on the buttons. The first button is the default one.
142 * @param modal Set it to {@code true} if you want the dialog to be modal
143 */
144 public ExtendedDialog(Component parent, String title, String[] buttonTexts, boolean modal) {
145 this(parent, title, buttonTexts, modal, true);
146 }
147
148 /**
149 * Same as above but lets you define if the dialog should be disposed on close.
150 * @param parent The parent element that will be used for position and maximum size
151 * @param title The text that will be shown in the window titlebar
152 * @param buttonTexts String Array of the text that will appear on the buttons. The first button is the default one.
153 * @param modal Set it to {@code true} if you want the dialog to be modal
154 * @param disposeOnClose whether to call {@link #dispose} when closing the dialog
155 */
156 public ExtendedDialog(Component parent, String title, String[] buttonTexts, boolean modal, boolean disposeOnClose) {
157 super(searchRealParent(parent), title, modal ? ModalityType.DOCUMENT_MODAL : ModalityType.MODELESS);
158 this.parent = parent;
159 this.modal = modal;
160 bTexts = Utils.copyArray(buttonTexts);
161 if (disposeOnClose) {
162 setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
163 }
164 this.disposeOnClose = disposeOnClose;
165 }
166
167 private static Frame searchRealParent(Component parent) {
168 if (parent == null) {
169 return null;
170 } else {
171 return GuiHelper.getFrameForComponent(parent);
172 }
173 }
174
175 @Override
176 public ExtendedDialog setButtonIcons(Icon... buttonIcons) {
177 this.bIcons = Utils.copyArray(buttonIcons);
178 return this;
179 }
180
181 @Override
182 public ExtendedDialog setButtonIcons(String... buttonIcons) {
183 bIcons = new Icon[buttonIcons.length];
184 for (int i = 0; i < buttonIcons.length; ++i) {
185 bIcons[i] = ImageProvider.get(buttonIcons[i]);
186 }
187 return this;
188 }
189
190 @Override
191 public ExtendedDialog setToolTipTexts(String... toolTipTexts) {
192 this.bToolTipTexts = Utils.copyArray(toolTipTexts);
193 return this;
194 }
195
196 @Override
197 public ExtendedDialog setContent(Component content) {
198 return setContent(content, true);
199 }
200
201 @Override
202 public ExtendedDialog setContent(Component content, boolean placeContentInScrollPane) {
203 this.content = content;
204 this.placeContentInScrollPane = placeContentInScrollPane;
205 return this;
206 }
207
208 @Override
209 public ExtendedDialog setContent(String message) {
210 return setContent(string2label(message), false);
211 }
212
213 @Override
214 public ExtendedDialog setIcon(Icon icon) {
215 this.icon = icon;
216 return this;
217 }
218
219 @Override
220 public ExtendedDialog setIcon(int messageType) {
221 switch (messageType) {
222 case JOptionPane.ERROR_MESSAGE:
223 return setIcon(UIManager.getIcon("OptionPane.errorIcon"));
224 case JOptionPane.INFORMATION_MESSAGE:
225 return setIcon(UIManager.getIcon("OptionPane.informationIcon"));
226 case JOptionPane.WARNING_MESSAGE:
227 return setIcon(UIManager.getIcon("OptionPane.warningIcon"));
228 case JOptionPane.QUESTION_MESSAGE:
229 return setIcon(UIManager.getIcon("OptionPane.questionIcon"));
230 case JOptionPane.PLAIN_MESSAGE:
231 return setIcon(null);
232 default:
233 throw new IllegalArgumentException("Unknown message type!");
234 }
235 }
236
237 @Override
238 public ExtendedDialog showDialog() {
239 // Check if the user has set the dialog to not be shown again
240 if (toggleCheckState()) {
241 result = toggleValue;
242 return this;
243 }
244
245 setupDialog();
246 if (defaultButton != null) {
247 getRootPane().setDefaultButton(defaultButton);
248 }
249 // Don't focus the "do not show this again" check box, but the default button.
250 if (toggleable || focusOnDefaultButton) {
251 requestFocusToDefaultButton();
252 }
253 setVisible(true);
254 toggleSaveState();
255 return this;
256 }
257
258 @Override
259 public int getValue() {
260 return result;
261 }
262
263 private boolean setupDone;
264
265 @Override
266 public void setupDialog() {
267 if (setupDone)
268 return;
269 setupDone = true;
270
271 setupEscListener();
272
273 JButton button;
274 JPanel buttonsPanel = new JPanel(new GridBagLayout());
275
276 for (int i = 0; i < bTexts.length; i++) {
277 button = new JButton(createButtonAction(i));
278 if (i == defaultButtonIdx-1) {
279 defaultButton = button;
280 }
281 if (bIcons != null && bIcons[i] != null) {
282 button.setIcon(bIcons[i]);
283 }
284 if (bToolTipTexts != null && i < bToolTipTexts.length && bToolTipTexts[i] != null) {
285 button.setToolTipText(bToolTipTexts[i]);
286 }
287
288 buttonsPanel.add(button, GBC.std().insets(2, 2, 2, 2));
289 buttons.add(button);
290 }
291 if (showHelpButton) {
292 buttonsPanel.add(new JButton(new HelpAction()), GBC.std().insets(2, 2, 2, 2));
293 HelpUtil.setHelpContext(getRootPane(), helpTopic);
294 }
295
296 JPanel cp = new JPanel(new GridBagLayout());
297
298 GridBagConstraints gc = new GridBagConstraints();
299 gc.gridx = 0;
300 int y = 0;
301 gc.gridy = y++;
302 gc.weightx = 0.0;
303 gc.weighty = 0.0;
304
305 if (icon != null) {
306 JLabel iconLbl = new JLabel(icon);
307 gc.insets = new Insets(10, 10, 10, 10);
308 gc.anchor = GridBagConstraints.NORTH;
309 gc.weighty = 1.0;
310 cp.add(iconLbl, gc);
311 gc.anchor = GridBagConstraints.CENTER;
312 gc.gridx = 1;
313 }
314
315 gc.fill = GridBagConstraints.BOTH;
316 gc.insets = contentInsets;
317 gc.weightx = 1.0;
318 gc.weighty = 1.0;
319 cp.add(content, gc);
320
321 gc.fill = GridBagConstraints.NONE;
322 gc.gridwidth = GridBagConstraints.REMAINDER;
323 gc.weightx = 0.0;
324 gc.weighty = 0.0;
325
326 if (toggleable) {
327 togglePanel = new ConditionalOptionPaneUtil.MessagePanel(null, ConditionalOptionPaneUtil.isInBulkOperation(togglePref));
328 gc.gridx = icon != null ? 1 : 0;
329 gc.gridy = y++;
330 gc.anchor = GridBagConstraints.LINE_START;
331 gc.insets = new Insets(5, contentInsets.left, 5, contentInsets.right);
332 cp.add(togglePanel, gc);
333 }
334
335 gc.gridy = y;
336 gc.anchor = GridBagConstraints.CENTER;
337 gc.insets = new Insets(5, 5, 5, 5);
338 cp.add(buttonsPanel, gc);
339 if (placeContentInScrollPane) {
340 JScrollPane pane = new JScrollPane(cp);
341 GuiHelper.setDefaultIncrement(pane);
342 pane.setBorder(null);
343 setContentPane(pane);
344 } else {
345 setContentPane(cp);
346 }
347 pack();
348
349 // Try to make it not larger than the parent window or at least not larger than 2/3 of the screen
350 Dimension d = getSize();
351 Dimension x = findMaxDialogSize();
352
353 boolean limitedInWidth = d.width > x.width;
354 boolean limitedInHeight = d.height > x.height;
355
356 if (x.width > 0 && d.width > x.width) {
357 d.width = x.width;
358 }
359 if (x.height > 0 && d.height > x.height) {
360 d.height = x.height;
361 }
362
363 // We have a vertical scrollbar and enough space to prevent a horizontal one
364 if (!limitedInWidth && limitedInHeight) {
365 d.width += new JScrollBar().getPreferredSize().width;
366 }
367
368 setSize(d);
369 setLocationRelativeTo(parent);
370 }
371
372 protected Action createButtonAction(final int i) {
373 return new AbstractAction(bTexts[i]) {
374 @Override
375 public void actionPerformed(ActionEvent evt) {
376 buttonAction(i, evt);
377 }
378 };
379 }
380
381 /**
382 * This gets performed whenever a button is clicked or activated
383 * @param buttonIndex the button index (first index is 0)
384 * @param evt the button event
385 */
386 protected void buttonAction(int buttonIndex, ActionEvent evt) {
387 result = buttonIndex+1;
388 setVisible(false);
389 }
390
391 /**
392 * Tries to find a good value of how large the dialog should be
393 * @return Dimension Size of the parent component if visible or 2/3 of screen size if not available or hidden
394 */
395 protected Dimension findMaxDialogSize() {
396 Dimension screenSize = GuiHelper.getScreenSize();
397 Dimension x = new Dimension(screenSize.width*2/3, screenSize.height*2/3);
398 if (parent != null && parent.isVisible()) {
399 x = GuiHelper.getFrameForComponent(parent).getSize();
400 }
401 return x;
402 }
403
404 /**
405 * Makes the dialog listen to ESC keypressed
406 */
407 private void setupEscListener() {
408 Action actionListener = new AbstractAction() {
409 @Override
410 public void actionPerformed(ActionEvent actionEvent) {
411 // 0 means that the dialog has been closed otherwise.
412 // We need to set it to zero again, in case the dialog has been re-used
413 // and the result differs from its default value
414 result = ExtendedDialog.DialogClosedOtherwise;
415 if (Main.isDebugEnabled()) {
416 Main.debug(getClass().getName()+" ESC action performed ("+actionEvent+") from "+new Exception().getStackTrace()[1]);
417 }
418 setVisible(false);
419 }
420 };
421
422 InputMapUtils.addEscapeAction(getRootPane(), actionListener);
423 }
424
425 protected final void rememberWindowGeometry(WindowGeometry geometry) {
426 if (geometry != null) {
427 geometry.remember(rememberSizePref);
428 }
429 }
430
431 protected final WindowGeometry initWindowGeometry() {
432 return new WindowGeometry(rememberSizePref, defaultWindowGeometry);
433 }
434
435 /**
436 * Override setVisible to be able to save the window geometry if required
437 */
438 @Override
439 public void setVisible(boolean visible) {
440 if (visible) {
441 repaint();
442 }
443
444 if (Main.isDebugEnabled()) {
445 Main.debug(getClass().getName()+".setVisible("+visible+") from "+new Exception().getStackTrace()[1]);
446 }
447
448 // Ensure all required variables are available
449 if (!rememberSizePref.isEmpty() && defaultWindowGeometry != null) {
450 if (visible) {
451 initWindowGeometry().applySafe(this);
452 } else if (isShowing()) { // should fix #6438, #6981, #8295
453 rememberWindowGeometry(new WindowGeometry(this));
454 }
455 }
456 super.setVisible(visible);
457
458 if (!visible && disposeOnClose) {
459 dispose();
460 }
461 }
462
463 @Override
464 public ExtendedDialog setRememberWindowGeometry(String pref, WindowGeometry wg) {
465 rememberSizePref = pref == null ? "" : pref;
466 defaultWindowGeometry = wg;
467 return this;
468 }
469
470 @Override
471 public ExtendedDialog toggleEnable(String togglePref) {
472 if (!modal) {
473 throw new IllegalStateException();
474 }
475 this.toggleable = true;
476 this.togglePref = togglePref;
477 return this;
478 }
479
480 @Override
481 public ExtendedDialog setDefaultButton(int defaultButtonIdx) {
482 this.defaultButtonIdx = defaultButtonIdx;
483 return this;
484 }
485
486 @Override
487 public ExtendedDialog setCancelButton(Integer... cancelButtonIdx) {
488 this.cancelButtonIdx = new HashSet<>(Arrays.<Integer>asList(cancelButtonIdx));
489 return this;
490 }
491
492 @Override
493 public void setFocusOnDefaultButton(boolean focus) {
494 focusOnDefaultButton = focus;
495 }
496
497 private void requestFocusToDefaultButton() {
498 if (defaultButton != null) {
499 GuiHelper.runInEDT(defaultButton::requestFocusInWindow);
500 }
501 }
502
503 @Override
504 public final boolean toggleCheckState() {
505 toggleable = togglePref != null && !togglePref.isEmpty();
506 toggleValue = ConditionalOptionPaneUtil.getDialogReturnValue(togglePref);
507 return toggleable && toggleValue != -1;
508 }
509
510 /**
511 * This function checks the state of the "Do not show again" checkbox and
512 * writes the corresponding pref.
513 */
514 protected void toggleSaveState() {
515 if (!toggleable ||
516 togglePanel == null ||
517 cancelButtonIdx.contains(result) ||
518 result == ExtendedDialog.DialogClosedOtherwise)
519 return;
520 togglePanel.getNotShowAgain().store(togglePref, result);
521 }
522
523 /**
524 * Convenience function that converts a given string into a JMultilineLabel
525 * @param msg the message to display
526 * @return JMultilineLabel displaying {@code msg}
527 */
528 private static JMultilineLabel string2label(String msg) {
529 JMultilineLabel lbl = new JMultilineLabel(msg);
530 // Make it not wider than 1/2 of the screen
531 Dimension screenSize = GuiHelper.getScreenSize();
532 lbl.setMaxWidth(screenSize.width/2);
533 // Disable default Enter key binding to allow dialog's one (then enables to hit default button from here)
534 lbl.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), new Object());
535 return lbl;
536 }
537
538 @Override
539 public ExtendedDialog configureContextsensitiveHelp(String helpTopic, boolean showHelpButton) {
540 this.helpTopic = helpTopic;
541 this.showHelpButton = showHelpButton;
542 return this;
543 }
544
545 class HelpAction extends AbstractAction {
546 /**
547 * Constructs a new {@code HelpAction}.
548 */
549 HelpAction() {
550 putValue(SHORT_DESCRIPTION, tr("Show help information"));
551 putValue(NAME, tr("Help"));
552 putValue(SMALL_ICON, ImageProvider.get("help"));
553 setEnabled(!Main.isOffline(OnlineResource.JOSM_WEBSITE));
554 }
555
556 @Override
557 public void actionPerformed(ActionEvent e) {
558 HelpBrowser.setUrlForHelpTopic(helpTopic);
559 }
560 }
561}
Note: See TracBrowser for help on using the repository browser.