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

Last change on this file since 2657 was 2627, checked in by bastiK, 14 years ago

geoimage: new button 'delelet image from disk' (shortcut: Ctrl+Shift+Del)

  • geoimage: shortcut for 'remove from layer' (Shift+Del)
  • ExtendedDialog: • option to choose the default button (reacts to ENTER)
    • option to better handle 'don't show again'
File size: 16.8 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.GridBagLayout;
8import java.awt.Toolkit;
9import java.awt.event.ActionEvent;
10import java.util.ArrayList;
11
12import javax.swing.AbstractAction;
13import javax.swing.Action;
14import javax.swing.JButton;
15import javax.swing.JCheckBox;
16import javax.swing.JComponent;
17import javax.swing.JDialog;
18import javax.swing.JOptionPane;
19import javax.swing.JPanel;
20import javax.swing.JScrollBar;
21import javax.swing.JScrollPane;
22import javax.swing.KeyStroke;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.gui.help.HelpBrowserProxy;
26import org.openstreetmap.josm.gui.help.HelpUtil;
27import org.openstreetmap.josm.tools.GBC;
28import org.openstreetmap.josm.tools.ImageProvider;
29import org.openstreetmap.josm.tools.WindowGeometry;
30
31public class ExtendedDialog extends JDialog {
32 private int result = 0;
33 public static final int DialogNotShown = -99;
34 public static final int DialogClosedOtherwise = 0;
35 private boolean toggleable = false;
36 private String rememberSizePref = "";
37 private WindowGeometry defaultWindowGeometry = null;
38 private String togglePref = "";
39 private String toggleCheckboxText = tr("Do not show again");
40 private JCheckBox toggleCheckbox = null;
41 private Component parent;
42 private Component content;
43 private final String[] bTexts;
44 private String[] bToolTipTexts;
45 private String[] bIcons;
46 private int cancelButtonIdx = -1;
47 private int defaultButtonIdx = -1;
48 private JButton defaultButton = null;
49
50 /** true, if the dialog should include a help button */
51 private boolean showHelpButton;
52 /** the help topic */
53 private String helpTopic;
54
55 /**
56 * set to true if the content of the extended dialog should
57 * be placed in a {@see JScrollPane}
58 */
59 private boolean placeContentInScrollPane;
60
61 // For easy access when inherited
62 protected Object contentConstraints = GBC.eol().anchor(GBC.CENTER).fill(GBC.BOTH).insets(5,10,5,0);
63 protected ArrayList<JButton> buttons = new ArrayList<JButton>();
64
65 /**
66 * This method sets up the most basic options for the dialog. Add all more
67 * advanced features with dedicated methods.
68 * Possible features:
69 * <ul>
70 * <li><code>setButtonIcons</code></li>
71 * <li><code>setContent</code></li>
72 * <li><code>toggleEnable</code></li>
73 * <li><code>toggleDisable</code></li>
74 * <li><code>setToggleCheckboxText</code></li>
75 * <li><code>setRememberWindowGeometry</code></li>
76 * </ul>
77 *
78 * When done, call <code>showDialog</code> to display it. You can receive
79 * the user's choice using <code>getValue</code>. Have a look at this function
80 * for possible return values.
81 *
82 * @param parent The parent element that will be used for position and maximum size
83 * @param title The text that will be shown in the window titlebar
84 * @param buttonTexts String Array of the text that will appear on the buttons. The first button is the default one.
85 */
86 public ExtendedDialog(Component parent, String title, String[] buttonTexts) {
87 super(JOptionPane.getFrameForComponent(parent), title, true);
88 this.parent = parent;
89 bTexts = buttonTexts;
90 }
91
92 /**
93 * Same as above but lets you define if the dialog should be modal.
94 */
95 public ExtendedDialog(Component parent, String title, String[] buttonTexts,
96 boolean modal) {
97 super(JOptionPane.getFrameForComponent(parent), title, modal);
98 this.parent = parent;
99 bTexts = buttonTexts;
100 }
101
102 /**
103 * Allows decorating the buttons with icons. Expects an String[] with paths
104 * to images relative to JOSM/images.
105 * @param buttonIcons
106 */
107 public ExtendedDialog setButtonIcons(String[] buttonIcons) {
108 this.bIcons = buttonIcons;
109 return this;
110 }
111
112 /**
113 * Allows decorating the buttons with tooltips. Expects an String[] with translated
114 * tooltip texts.
115 *
116 * @param toolTipTexts the tool tip texts. Ignored, if null.
117 */
118 public ExtendedDialog setToolTipTexts(String[] toolTipTexts) {
119 this.bToolTipTexts = toolTipTexts;
120 return this;
121 }
122
123 /**
124 * Sets the content that will be displayed in the message dialog.
125 *
126 * Note that depending on your other settings more UI elements may appear.
127 * The content is played on top of the other elements though.
128 *
129 * @param content Any element that can be displayed in the message dialog
130 */
131 public ExtendedDialog setContent(Component content) {
132 return setContent(content, true);
133 }
134
135 /**
136 * Sets the content that will be displayed in the message dialog.
137 *
138 * Note that depending on your other settings more UI elements may appear.
139 * The content is played on top of the other elements though.
140 *
141 * @param content Any element that can be displayed in the message dialog
142 * @param placeContentInScrollPane if true, places the content in a JScrollPane
143 *
144 */
145 public ExtendedDialog setContent(Component content, boolean placeContentInScrollPane) {
146 this.content = content;
147 this.placeContentInScrollPane = placeContentInScrollPane;
148 return this;
149 }
150
151 /**
152 * Sets the message that will be displayed. The String will be automatically
153 * wrapped if it is too long.
154 *
155 * Note that depending on your other settings more UI elements may appear.
156 * The content is played on top of the other elements though.
157 *
158 * @param message The text that should be shown to the user
159 */
160 public ExtendedDialog setContent(String message) {
161 return setContent(string2label(message), false);
162 }
163
164 /**
165 * Show the dialog to the user. Call this after you have set all options
166 * for the dialog. You can retrieve the result using <code>getValue</code>
167 */
168 public ExtendedDialog showDialog() {
169 // Check if the user has set the dialog to not be shown again
170 if(toggleCheckState(togglePref)) {
171 result = ExtendedDialog.DialogNotShown;
172 return this;
173 }
174
175 setupDialog();
176 if (defaultButton != null) {
177 getRootPane().setDefaultButton(defaultButton);
178 }
179 setVisible(true);
180 toggleSaveState();
181 return this;
182 }
183
184 /**
185 * @return int * The selected button. The count starts with 1.
186 * * A return value of ExtendedDialog.DialogClosedOtherwise means the dialog has been closed otherwise.
187 * * A return value of ExtendedDialog.DialogNotShown means the
188 * dialog has been toggled off in the past
189 */
190 public int getValue() {
191 return result;
192 }
193
194 protected void setupDialog() {
195 setupEscListener();
196
197 JButton button;
198 JPanel buttonsPanel = new JPanel(new GridBagLayout());
199
200 for(int i=0; i < bTexts.length; i++) {
201 Action action = new AbstractAction(bTexts[i]) {
202 public void actionPerformed(ActionEvent evt) {
203 buttonAction(evt);
204 }
205 };
206
207 button = new JButton(action);
208 if (i == defaultButtonIdx-1) {
209 defaultButton = button;
210 }
211 if(bIcons != null && bIcons[i] != null) {
212 button.setIcon(ImageProvider.get(bIcons[i]));
213 }
214 if (bToolTipTexts != null && i < bToolTipTexts.length && bToolTipTexts[i] != null) {
215 button.setToolTipText(bToolTipTexts[i]);
216 }
217
218 if(i == 0) {
219 rootPane.setDefaultButton(button);
220 }
221 buttonsPanel.add(button, GBC.std().insets(2,2,2,2));
222 buttons.add(button);
223 }
224 if (showHelpButton) {
225 buttonsPanel.add(new JButton(new HelpAction()), GBC.std().insets(2,2,2,2));
226 HelpUtil.setHelpContext(getRootPane(),helpTopic);
227 }
228
229 JPanel cp = new JPanel(new GridBagLayout());
230 cp.add(content, contentConstraints);
231
232 if(toggleable) {
233 toggleCheckbox = new JCheckBox(toggleCheckboxText);
234 boolean showDialog = Main.pref.getBoolean("message."+ togglePref, true);
235 toggleCheckbox.setSelected(!showDialog);
236 cp.add(toggleCheckbox, GBC.eol().anchor(GBC.LINE_START).insets(5,5,5,5));
237 }
238
239 cp.add(buttonsPanel, GBC.eol().anchor(GBC.CENTER).insets(5,5,5,5));
240 if (placeContentInScrollPane) {
241 JScrollPane pane = new JScrollPane(cp);
242 pane.setBorder(null);
243 setContentPane(pane);
244 } else {
245 setContentPane(cp);
246 }
247 pack();
248
249 // Try to make it not larger than the parent window or at least not larger than 2/3 of the screen
250 Dimension d = getSize();
251 Dimension x = findMaxDialogSize();
252
253 boolean limitedInWidth = d.width > x.width;
254 boolean limitedInHeight = d.height > x.height;
255
256 if(x.width > 0 && d.width > x.width) {
257 d.width = x.width;
258 }
259 if(x.height > 0 && d.height > x.height) {
260 d.height = x.height;
261 }
262
263 // We have a vertical scrollbar and enough space to prevent a horizontal one
264 if(!limitedInWidth && limitedInHeight) {
265 d.width += new JScrollBar().getPreferredSize().width;
266 }
267
268 setSize(d);
269 setLocationRelativeTo(parent);
270 }
271
272 /**
273 * This gets performed whenever a button is clicked or activated
274 * @param evt the button event
275 */
276 protected void buttonAction(ActionEvent evt) {
277 String a = evt.getActionCommand();
278 for(int i=0; i < bTexts.length; i++)
279 if(bTexts[i].equals(a)) {
280 result = i+1;
281 break;
282 }
283
284 setVisible(false);
285 }
286
287 /**
288 * Tries to find a good value of how large the dialog should be
289 * @return Dimension Size of the parent Component or 2/3 of screen size if not available
290 */
291 protected Dimension findMaxDialogSize() {
292 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
293 Dimension x = new Dimension(screenSize.width*2/3, screenSize.height*2/3);
294 try {
295 if(parent != null) {
296 x = JOptionPane.getFrameForComponent(parent).getSize();
297 }
298 } catch(NullPointerException e) { }
299 return x;
300 }
301
302 /**
303 * Makes the dialog listen to ESC keypressed
304 */
305 private void setupEscListener() {
306 Action actionListener = new AbstractAction() {
307 public void actionPerformed(ActionEvent actionEvent) {
308 // 0 means that the dialog has been closed otherwise.
309 // We need to set it to zero again, in case the dialog has been re-used
310 // and the result differs from its default value
311 result = ExtendedDialog.DialogClosedOtherwise;
312 setVisible(false);
313 }
314 };
315
316 getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
317 .put(KeyStroke.getKeyStroke("ESCAPE"), "ESCAPE");
318 getRootPane().getActionMap().put("ESCAPE", actionListener);
319 }
320
321 /**
322 * Override setVisible to be able to save the window geometry if required
323 */
324 @Override
325 public void setVisible(boolean visible) {
326 if (visible) {
327 repaint();
328 }
329
330 // Ensure all required variables are available
331 if(rememberSizePref.length() != 0 && defaultWindowGeometry != null) {
332 if(visible) {
333 new WindowGeometry(rememberSizePref,
334 defaultWindowGeometry).apply(this);
335 } else {
336 new WindowGeometry(this).remember(rememberSizePref);
337 }
338 }
339 super.setVisible(visible);
340 }
341
342 /**
343 * Call this if you want the dialog to remember the size set by the user.
344 * Set the pref to <code>null</code> or to an empty string to disable again.
345 * By default, it's disabled.
346 *
347 * Note: If you want to set the width of this dialog directly use the usual
348 * setSize, setPreferredSize, setMaxSize, setMinSize
349 *
350 * @param pref The preference to save the dimension to
351 * @param wg The default window geometry that should be used if no
352 * existing preference is found (only takes effect if
353 * <code>pref</code> is not null or empty
354 *
355 */
356 public ExtendedDialog setRememberWindowGeometry(String pref, WindowGeometry wg) {
357 rememberSizePref = pref == null ? "" : pref;
358 defaultWindowGeometry = wg;
359 return this;
360 }
361
362 /**
363 * Calling this will offer the user a "Do not show again" checkbox for the
364 * dialog. Default is to not offer the choice; the dialog will be shown
365 * every time. If the dialog is not shown due to the previous choice of the
366 * user, the result <code>ExtendedDialog.DialogNotShown</code> is returned
367 * @param togglePref The preference to save the checkbox state to
368 */
369 public ExtendedDialog toggleEnable(String togglePref) {
370 this.toggleable = true;
371 this.togglePref = togglePref;
372 return this;
373 }
374
375 /**
376 * Call this if you "accidentally" called toggleEnable. This doesn't need
377 * to be called for every dialog, as it's the default anyway.
378 */
379 public ExtendedDialog toggleDisable() {
380 this.toggleable = false;
381 return this;
382 }
383
384 /**
385 * Overwrites the default "Don't show again" text of the toggle checkbox
386 * if you want to give more information. Only has an effect if
387 * <code>toggleEnable</code> is set.
388 * @param text
389 */
390 public ExtendedDialog setToggleCheckboxText(String text) {
391 this.toggleCheckboxText = text;
392 return this;
393 }
394
395 /**
396 * Sets the button that will react to ENTER.
397 */
398 public ExtendedDialog setDefaultButton(int defaultButtonIdx) {
399 this.defaultButtonIdx = defaultButtonIdx;
400 return this;
401 }
402
403 /**
404 * Used in combination with toggle:
405 * If the user presses 'cancel' the toggle settings are ignored and not saved to the pref
406 * @param cancelButton index of the button that stands for cancel
407 */
408 public ExtendedDialog setCancelButton(int cancelButtonIdx) {
409 this.cancelButtonIdx = cancelButtonIdx;
410 return this;
411 }
412
413 /**
414 * This function returns true if the dialog has been set to "do not show again"
415 * @return true if dialog should not be shown again
416 */
417 private boolean toggleCheckState(String togglePref) {
418 toggleable = togglePref != null && !togglePref.equals("");
419
420 // No identifier given, so return false (= show the dialog)
421 if(!toggleable)
422 return false;
423 this.togglePref = togglePref;
424 // The pref is true, if the dialog should be shown.
425 return !(Main.pref.getBoolean("message."+ togglePref, true));
426 }
427
428 /**
429 * This function checks the state of the "Do not show again" checkbox and
430 * writes the corresponding pref
431 */
432 private void toggleSaveState() {
433 if(!toggleable || toggleCheckbox == null || result == cancelButtonIdx || result == ExtendedDialog.DialogClosedOtherwise)
434 return;
435 Main.pref.put("message."+ togglePref, !toggleCheckbox.isSelected());
436 }
437
438 /**
439 * Convenience function that converts a given string into a JMultilineLabel
440 * @param msg
441 * @return JMultilineLabel
442 */
443 private static JMultilineLabel string2label(String msg) {
444 JMultilineLabel lbl = new JMultilineLabel(msg);
445 // Make it not wider than 1/2 of the screen
446 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
447 lbl.setMaxWidth(screenSize.width/2);
448 return lbl;
449 }
450
451 /**
452 * Configures how this dialog support for context sensitive help.
453 * <ul>
454 * <li>if helpTopic is null, the dialog doesn't provide context sensitive help</li>
455 * <li>if helpTopic != null, the dialog redirect user to the help page for this helpTopic when
456 * the user clicks F1 in the dialog</li>
457 * <li>if showHelpButton is true, the dialog displays "Help" button (rightmost button in
458 * the button row)</li>
459 * </ul>
460 *
461 * @param helpTopic the help topic
462 * @param showHelpButton true, if the dialog displays a help button
463 */
464 public ExtendedDialog configureContextsensitiveHelp(String helpTopic, boolean showHelpButton) {
465 this.helpTopic = helpTopic;
466 this.showHelpButton = showHelpButton;
467 return this;
468 }
469
470 class HelpAction extends AbstractAction {
471 public HelpAction() {
472 putValue(SHORT_DESCRIPTION, tr("Show help information"));
473 putValue(NAME, tr("Help"));
474 putValue(SMALL_ICON, ImageProvider.get("help"));
475 }
476
477 public void actionPerformed(ActionEvent e) {
478 HelpBrowserProxy.getInstance().setUrlForHelpTopic(helpTopic);
479 }
480 }
481}
Note: See TracBrowser for help on using the repository browser.