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

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

some minor things:

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