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

Last change on this file since 3711 was 3711, checked in by bastiK, 13 years ago

minor change needed for terracer plugin

  • Property svn:eol-style set to native
File size: 21.0 KB
Line 
1package org.openstreetmap.josm.gui;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.awt.Component;
6import java.awt.Dimension;
7import java.awt.GridBagConstraints;
8import java.awt.GridBagLayout;
9import java.awt.Insets;
10import java.awt.Toolkit;
11import java.awt.event.ActionEvent;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collections;
15import java.util.List;
16
17import javax.swing.AbstractAction;
18import javax.swing.Action;
19import javax.swing.Icon;
20import javax.swing.JButton;
21import javax.swing.JCheckBox;
22import javax.swing.JComponent;
23import javax.swing.JDialog;
24import javax.swing.JLabel;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.JScrollBar;
28import javax.swing.JScrollPane;
29import javax.swing.KeyStroke;
30import javax.swing.SwingUtilities;
31import javax.swing.UIManager;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.gui.help.HelpBrowser;
35import org.openstreetmap.josm.gui.help.HelpUtil;
36import org.openstreetmap.josm.tools.GBC;
37import org.openstreetmap.josm.tools.ImageProvider;
38import org.openstreetmap.josm.tools.WindowGeometry;
39
40/**
41 * General configurable dialog window.
42 *
43 * If dialog is modal, you can use getValue() to retrieve the
44 * button index. Note that the user can close the dialog
45 * by other means. This is usually equivalent to cancel action.
46 *
47 * For non-modal dialogs, buttonAction(int) can be overridden.
48 *
49 * There are various options, see below.
50 *
51 * Note: The button indices are counted from 1 and upwards.
52 * So for getValue(), setDefaultButton(int) and setCancelButton(int) the
53 * first button has index 1.
54 */
55public class ExtendedDialog extends JDialog {
56 private final boolean disposeOnClose;
57 private int result = 0;
58 public static final int DialogClosedOtherwise = 0;
59 private boolean toggleable = false;
60 private String rememberSizePref = "";
61 private WindowGeometry defaultWindowGeometry = null;
62 private String togglePref = "";
63 private int toggleValue = -1;
64 private String toggleCheckboxText = tr("Do not show again (remembers choice)");
65 private JCheckBox toggleCheckbox = null;
66 private Component parent;
67 private Component content;
68 private final String[] bTexts;
69 private String[] bToolTipTexts;
70 private Icon[] bIcons;
71 private List<Integer> cancelButtonIdx = Collections.emptyList();
72 private int defaultButtonIdx = 1;
73 protected JButton defaultButton = null;
74 private Icon icon;
75 private boolean modal;
76
77 /** true, if the dialog should include a help button */
78 private boolean showHelpButton;
79 /** the help topic */
80 private String helpTopic;
81
82 /**
83 * set to true if the content of the extended dialog should
84 * be placed in a {@see JScrollPane}
85 */
86 private boolean placeContentInScrollPane;
87
88 // For easy access when inherited
89 protected Insets contentInsets = new Insets(10,5,0,5);
90 protected ArrayList<JButton> buttons = new ArrayList<JButton>();
91
92 /**
93 * This method sets up the most basic options for the dialog. Add more
94 * advanced features with dedicated methods.
95 * Possible features:
96 * <ul>
97 * <li><code>setButtonIcons</code></li>
98 * <li><code>setContent</code></li>
99 * <li><code>toggleEnable</code></li>
100 * <li><code>toggleDisable</code></li>
101 * <li><code>setToggleCheckboxText</code></li>
102 * <li><code>setRememberWindowGeometry</code></li>
103 * </ul>
104 *
105 * When done, call <code>showDialog</code> to display it. You can receive
106 * the user's choice using <code>getValue</code>. Have a look at this function
107 * for possible return values.
108 *
109 * @param parent The parent element that will be used for position and maximum size
110 * @param title The text that will be shown in the window titlebar
111 * @param buttonTexts String Array of the text that will appear on the buttons. The first button is the default one.
112 */
113 public ExtendedDialog(Component parent, String title, String[] buttonTexts) {
114 this(parent, title, buttonTexts, true, true);
115 }
116
117 /**
118 * Same as above but lets you define if the dialog should be modal.
119 */
120 public ExtendedDialog(Component parent, String title, String[] buttonTexts,
121 boolean modal) {
122 this(parent, title, buttonTexts, modal, true);
123 }
124
125 public ExtendedDialog(Component parent, String title, String[] buttonTexts,
126 boolean modal, boolean disposeOnClose) {
127 super(JOptionPane.getFrameForComponent(parent), title, modal ? ModalityType.DOCUMENT_MODAL : ModalityType.MODELESS);
128 this.parent = parent;
129 this.modal = modal;
130 bTexts = buttonTexts;
131 if (disposeOnClose) {
132 setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
133 }
134 this.disposeOnClose = disposeOnClose;
135 }
136
137 /**
138 * Allows decorating the buttons with icons.
139 * @param buttonIcons
140 */
141 public ExtendedDialog setButtonIcons(Icon[] buttonIcons) {
142 this.bIcons = buttonIcons;
143 return this;
144 }
145
146 /**
147 * Convenience method to provide image names instead of images.
148 */
149 public ExtendedDialog setButtonIcons(String[] buttonIcons) {
150 bIcons = new Icon[buttonIcons.length];
151 for (int i=0; i<buttonIcons.length; ++i) {
152 bIcons[i] = ImageProvider.get(buttonIcons[i]);
153 }
154 return this;
155 }
156
157 /**
158 * Allows decorating the buttons with tooltips. Expects an String[] with translated
159 * tooltip texts.
160 *
161 * @param toolTipTexts the tool tip texts. Ignored, if null.
162 */
163 public ExtendedDialog setToolTipTexts(String[] toolTipTexts) {
164 this.bToolTipTexts = toolTipTexts;
165 return this;
166 }
167
168 /**
169 * Sets the content that will be displayed in the message dialog.
170 *
171 * Note that depending on your other settings more UI elements may appear.
172 * The content is played on top of the other elements though.
173 *
174 * @param content Any element that can be displayed in the message dialog
175 */
176 public ExtendedDialog setContent(Component content) {
177 return setContent(content, true);
178 }
179
180 /**
181 * Sets the content that will be displayed in the message dialog.
182 *
183 * Note that depending on your other settings more UI elements may appear.
184 * The content is played on top of the other elements though.
185 *
186 * @param content Any element that can be displayed in the message dialog
187 * @param placeContentInScrollPane if true, places the content in a JScrollPane
188 *
189 */
190 public ExtendedDialog setContent(Component content, boolean placeContentInScrollPane) {
191 this.content = content;
192 this.placeContentInScrollPane = placeContentInScrollPane;
193 return this;
194 }
195
196 /**
197 * Sets the message that will be displayed. The String will be automatically
198 * wrapped if it is too long.
199 *
200 * Note that depending on your other settings more UI elements may appear.
201 * The content is played on top of the other elements though.
202 *
203 * @param message The text that should be shown to the user
204 */
205 public ExtendedDialog setContent(String message) {
206 return setContent(string2label(message), false);
207 }
208
209 /**
210 * Decorate the dialog with an icon that is shown on the left part of
211 * the window area. (Similar to how it is done in JOptionPane)
212 */
213 public ExtendedDialog setIcon(Icon icon) {
214 this.icon = icon;
215 return this;
216 }
217
218 /**
219 * Convenience method to allow values that would be accepted by JOptionPane as messageType.
220 */
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 /**
239 * Show the dialog to the user. Call this after you have set all options
240 * for the dialog. You can retrieve the result using <code>getValue</code>
241 */
242 public ExtendedDialog showDialog() {
243 // Check if the user has set the dialog to not be shown again
244 if (toggleCheckState(togglePref)) {
245 result = toggleValue;
246 return this;
247 }
248
249 setupDialog();
250 if (defaultButton != null) {
251 getRootPane().setDefaultButton(defaultButton);
252 }
253 fixFocus();
254 setVisible(true);
255 toggleSaveState();
256 return this;
257 }
258
259 /**
260 * @return int * The selected button. The count starts with 1.
261 * * A return value of ExtendedDialog.DialogClosedOtherwise means the dialog has been closed otherwise.
262 */
263 public int getValue() {
264 return result;
265 }
266
267 private boolean setupDone = false;
268
269 /**
270 * This is called by showDialog().
271 * Only invoke from outside if you need to modify the contentPane
272 */
273 public void setupDialog() {
274 if (setupDone)
275 return;
276 setupDone = true;
277
278 setupEscListener();
279
280 JButton button;
281 JPanel buttonsPanel = new JPanel(new GridBagLayout());
282
283 for(int i=0; i < bTexts.length; i++) {
284 final int final_i = i;
285 Action action = new AbstractAction(bTexts[i]) {
286 public void actionPerformed(ActionEvent evt) {
287 buttonAction(final_i, evt);
288 }
289 };
290
291 button = new JButton(action);
292 if (i == defaultButtonIdx-1) {
293 defaultButton = button;
294 }
295 if(bIcons != null && bIcons[i] != null) {
296 button.setIcon(bIcons[i]);
297 }
298 if (bToolTipTexts != null && i < bToolTipTexts.length && bToolTipTexts[i] != null) {
299 button.setToolTipText(bToolTipTexts[i]);
300 }
301
302 buttonsPanel.add(button, GBC.std().insets(2,2,2,2));
303 buttons.add(button);
304 }
305 if (showHelpButton) {
306 buttonsPanel.add(new JButton(new HelpAction()), GBC.std().insets(2,2,2,2));
307 HelpUtil.setHelpContext(getRootPane(),helpTopic);
308 }
309
310 JPanel cp = new JPanel(new GridBagLayout());
311
312 GridBagConstraints gc = new GridBagConstraints();
313 gc.gridx = 0;
314 int y = 0;
315 gc.gridy = y++;
316 gc.weightx = 0.0;
317 gc.weighty = 0.0;
318
319 if (icon != null) {
320 JLabel iconLbl = new JLabel(icon);
321 gc.insets = new Insets(10,10,10,10);
322 gc.anchor = GridBagConstraints.NORTH;
323 gc.weighty = 1.0;
324 cp.add(iconLbl, gc);
325 gc.anchor = GridBagConstraints.CENTER;
326 gc.gridx = 1;
327 }
328
329 gc.fill = GridBagConstraints.BOTH;
330 gc.insets = contentInsets;
331 gc.weightx = 1.0;
332 gc.weighty = 1.0;
333 cp.add(content, gc);
334
335 gc.fill = GridBagConstraints.NONE;
336 gc.gridwidth = GridBagConstraints.REMAINDER;
337 gc.weightx = 0.0;
338 gc.weighty = 0.0;
339
340 if (toggleable) {
341 toggleCheckbox = new JCheckBox(toggleCheckboxText);
342 boolean showDialog = Main.pref.getBoolean("message."+ togglePref, true);
343 toggleCheckbox.setSelected(!showDialog);
344 gc.gridx = icon != null ? 1 : 0;
345 gc.gridy = y++;
346 gc.anchor = GridBagConstraints.LINE_START;
347 gc.insets = new Insets(5,contentInsets.left,5,contentInsets.right);
348 cp.add(toggleCheckbox, gc);
349 }
350
351 gc.gridy = y++;
352 gc.anchor = GridBagConstraints.CENTER;
353 gc.insets = new Insets(5,5,5,5);
354 cp.add(buttonsPanel, gc);
355 if (placeContentInScrollPane) {
356 JScrollPane pane = new JScrollPane(cp);
357 pane.setBorder(null);
358 setContentPane(pane);
359 } else {
360 setContentPane(cp);
361 }
362 pack();
363
364 // Try to make it not larger than the parent window or at least not larger than 2/3 of the screen
365 Dimension d = getSize();
366 Dimension x = findMaxDialogSize();
367
368 boolean limitedInWidth = d.width > x.width;
369 boolean limitedInHeight = d.height > x.height;
370
371 if(x.width > 0 && d.width > x.width) {
372 d.width = x.width;
373 }
374 if(x.height > 0 && d.height > x.height) {
375 d.height = x.height;
376 }
377
378 // We have a vertical scrollbar and enough space to prevent a horizontal one
379 if(!limitedInWidth && limitedInHeight) {
380 d.width += new JScrollBar().getPreferredSize().width;
381 }
382
383 setSize(d);
384 setLocationRelativeTo(parent);
385 }
386
387 /**
388 * This gets performed whenever a button is clicked or activated
389 * @param evt the button event
390 */
391 protected void buttonAction(int buttonIndex, ActionEvent evt) {
392 result = buttonIndex+1;
393 setVisible(false);
394 }
395
396 /**
397 * Tries to find a good value of how large the dialog should be
398 * @return Dimension Size of the parent Component or 2/3 of screen size if not available
399 */
400 protected Dimension findMaxDialogSize() {
401 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
402 Dimension x = new Dimension(screenSize.width*2/3, screenSize.height*2/3);
403 try {
404 if(parent != null) {
405 x = JOptionPane.getFrameForComponent(parent).getSize();
406 }
407 } catch(NullPointerException e) { }
408 return x;
409 }
410
411 /**
412 * Makes the dialog listen to ESC keypressed
413 */
414 private void setupEscListener() {
415 Action actionListener = new AbstractAction() {
416 public void actionPerformed(ActionEvent actionEvent) {
417 // 0 means that the dialog has been closed otherwise.
418 // We need to set it to zero again, in case the dialog has been re-used
419 // and the result differs from its default value
420 result = ExtendedDialog.DialogClosedOtherwise;
421 setVisible(false);
422 }
423 };
424
425 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
426 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
427 getRootPane().getActionMap().put("ESCAPE", actionListener);
428 }
429
430 /**
431 * Override setVisible to be able to save the window geometry if required
432 */
433 @Override
434 public void setVisible(boolean visible) {
435 if (visible) {
436 repaint();
437 }
438
439 // Ensure all required variables are available
440 if(rememberSizePref.length() != 0 && defaultWindowGeometry != null) {
441 if(visible) {
442 new WindowGeometry(rememberSizePref,
443 defaultWindowGeometry).applySafe(this);
444 } else {
445 new WindowGeometry(this).remember(rememberSizePref);
446 }
447 }
448 super.setVisible(visible);
449
450 if (!visible && disposeOnClose) {
451 dispose();
452 }
453 }
454
455 /**
456 * Call this if you want the dialog to remember the size set by the user.
457 * Set the pref to <code>null</code> or to an empty string to disable again.
458 * By default, it's disabled.
459 *
460 * Note: If you want to set the width of this dialog directly use the usual
461 * setSize, setPreferredSize, setMaxSize, setMinSize
462 *
463 * @param pref The preference to save the dimension to
464 * @param wg The default window geometry that should be used if no
465 * existing preference is found (only takes effect if
466 * <code>pref</code> is not null or empty
467 *
468 */
469 public ExtendedDialog setRememberWindowGeometry(String pref, WindowGeometry wg) {
470 rememberSizePref = pref == null ? "" : pref;
471 defaultWindowGeometry = wg;
472 return this;
473 }
474
475 /**
476 * Calling this will offer the user a "Do not show again" checkbox for the
477 * dialog. Default is to not offer the choice; the dialog will be shown
478 * every time.
479 * Currently, this is not supported for non-modal dialogs.
480 * @param togglePref The preference to save the checkbox state to
481 */
482 public ExtendedDialog toggleEnable(String togglePref) {
483 if (!modal) {
484 throw new IllegalArgumentException();
485 }
486 this.toggleable = true;
487 this.togglePref = togglePref;
488 return this;
489 }
490
491 /**
492 * Call this if you "accidentally" called toggleEnable. This doesn't need
493 * to be called for every dialog, as it's the default anyway.
494 */
495 public ExtendedDialog toggleDisable() {
496 this.toggleable = false;
497 return this;
498 }
499
500 /**
501 * Overwrites the default "Don't show again" text of the toggle checkbox
502 * if you want to give more information. Only has an effect if
503 * <code>toggleEnable</code> is set.
504 * @param text
505 */
506 public ExtendedDialog setToggleCheckboxText(String text) {
507 this.toggleCheckboxText = text;
508 return this;
509 }
510
511 /**
512 * Sets the button that will react to ENTER.
513 */
514 public ExtendedDialog setDefaultButton(int defaultButtonIdx) {
515 this.defaultButtonIdx = defaultButtonIdx;
516 return this;
517 }
518
519 /**
520 * Used in combination with toggle:
521 * If the user presses 'cancel' the toggle settings are ignored and not saved to the pref
522 * @param cancelButton index of the button that stands for cancel, accepts
523 * multiple values
524 */
525 public ExtendedDialog setCancelButton(Integer... cancelButtonIdx) {
526 this.cancelButtonIdx = Arrays.<Integer>asList(cancelButtonIdx);
527 return this;
528 }
529
530 /**
531 * Don't focus the "do not show this again" check box, but the default button.
532 */
533 protected void fixFocus() {
534 if (toggleable && defaultButton != null) {
535 SwingUtilities.invokeLater(new Runnable() {
536 public void run() {
537 defaultButton.requestFocusInWindow();
538 }
539 });
540 }
541 }
542
543 /**
544 * This function returns true if the dialog has been set to "do not show again"
545 * @return true if dialog should not be shown again
546 */
547 private boolean toggleCheckState(String togglePref) {
548 toggleable = togglePref != null && !togglePref.equals("");
549
550 toggleValue = Main.pref.getInteger("message."+togglePref+".value", -1);
551 // No identifier given, so return false (= show the dialog)
552 if(!toggleable || toggleValue == -1)
553 return false;
554 this.togglePref = togglePref;
555 // The pref is true, if the dialog should be shown.
556 return !(Main.pref.getBoolean("message."+ togglePref, true));
557 }
558
559 /**
560 * This function checks the state of the "Do not show again" checkbox and
561 * writes the corresponding pref
562 */
563 private void toggleSaveState() {
564 if (!toggleable ||
565 toggleCheckbox == null ||
566 cancelButtonIdx.contains(result) ||
567 result == ExtendedDialog.DialogClosedOtherwise)
568 return;
569 Main.pref.put("message."+ togglePref, !toggleCheckbox.isSelected());
570 Main.pref.putInteger("message."+togglePref+".value", result);
571 }
572
573 /**
574 * Convenience function that converts a given string into a JMultilineLabel
575 * @param msg
576 * @return JMultilineLabel
577 */
578 private static JMultilineLabel string2label(String msg) {
579 JMultilineLabel lbl = new JMultilineLabel(msg);
580 // Make it not wider than 1/2 of the screen
581 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
582 lbl.setMaxWidth(screenSize.width/2);
583 return lbl;
584 }
585
586 /**
587 * Configures how this dialog support for context sensitive help.
588 * <ul>
589 * <li>if helpTopic is null, the dialog doesn't provide context sensitive help</li>
590 * <li>if helpTopic != null, the dialog redirect user to the help page for this helpTopic when
591 * the user clicks F1 in the dialog</li>
592 * <li>if showHelpButton is true, the dialog displays "Help" button (rightmost button in
593 * the button row)</li>
594 * </ul>
595 *
596 * @param helpTopic the help topic
597 * @param showHelpButton true, if the dialog displays a help button
598 */
599 public ExtendedDialog configureContextsensitiveHelp(String helpTopic, boolean showHelpButton) {
600 this.helpTopic = helpTopic;
601 this.showHelpButton = showHelpButton;
602 return this;
603 }
604
605 class HelpAction extends AbstractAction {
606 public HelpAction() {
607 putValue(SHORT_DESCRIPTION, tr("Show help information"));
608 putValue(NAME, tr("Help"));
609 putValue(SMALL_ICON, ImageProvider.get("help"));
610 }
611
612 public void actionPerformed(ActionEvent e) {
613 HelpBrowser.setUrlForHelpTopic(helpTopic);
614 }
615 }
616}
Note: See TracBrowser for help on using the repository browser.