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

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

see #15182 - move WindowGeometry from tools to gui.util

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