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

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

added missing license information

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