1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.xnap.commons.gui;
22
23 import java.awt.BorderLayout;
24 import java.awt.Component;
25 import java.awt.Dialog;
26 import java.awt.Dimension;
27 import java.awt.FlowLayout;
28 import java.awt.Frame;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ComponentAdapter;
31 import java.awt.event.ComponentEvent;
32 import java.awt.event.KeyEvent;
33 import java.awt.event.WindowAdapter;
34 import javax.swing.AbstractAction;
35 import javax.swing.Action;
36 import javax.swing.BorderFactory;
37 import javax.swing.Box;
38 import javax.swing.JButton;
39 import javax.swing.JComponent;
40 import javax.swing.JDialog;
41 import javax.swing.JPanel;
42 import javax.swing.JSeparator;
43 import javax.swing.border.EmptyBorder;
44 import org.xnap.commons.gui.util.GUIHelper;
45 import org.xnap.commons.gui.util.WhatsThis;
46 import org.xnap.commons.gui.util.WhatsThisAction;
47 import org.xnap.commons.i18n.I18n;
48 import org.xnap.commons.i18n.I18nFactory;
49 import org.xnap.commons.util.SystemHelper;
50
51 /***
52 * This class provides a default implementation for a dialog. A dialog consists
53 * of two areas. The main area, located at the center of the dialog, contains
54 * the user interaction components. The button area, located at the south
55 * west of the dialog, is surrounded by an empty border and contains the
56 * buttons.
57 *
58 * <p>The most common buttons are provided with default actions.</p>
59 *
60 * <p>When you extend this class make sure <code>pack()</code> is called after
61 * all components have been added.</p>
62 */
63 public class DefaultDialog extends JDialog {
64
65
66
67
68
69
70
71
72
73
74
75 private static final I18n i18n = I18nFactory.getI18n(DefaultDialog.class);
76
77 /***
78 * The button type for no buttons.
79 */
80 public static int BUTTON_NONE = 0;
81
82 /***
83 * The button type for the okay button.
84 */
85 public static int BUTTON_OKAY = 1;
86
87 /***
88 * The button type for the apply button.
89 */
90 public static int BUTTON_APPLY = 2;
91
92 /***
93 * The button type for the cancel button.
94 */
95 public static int BUTTON_CANCEL = 4;
96
97 /***
98 * The button type for the close button.
99 */
100 public static int BUTTON_CLOSE = 8;
101
102 /***
103 * The button type for the help button.
104 */
105 public static int BUTTON_HELP = 16;
106
107 /***
108 * The button type for the context help button.
109 */
110 public static int BUTTON_CONTEXT_HELP = 32;
111
112 /***
113 * The button type for the defaults button.
114 */
115 public static int BUTTON_DEFAULTS = 64;
116
117 private ApplyAction applyAction = new ApplyAction();
118 private CancelAction cancelAction = new CancelAction();
119 private CloseAction closeAction = new CloseAction();
120 private OkayAction okayAction = new OkayAction();
121 private HelpAction helpAction = new HelpAction();
122 private DefaultsAction defaultsAction = new DefaultsAction();
123 private ContextHelpAction contextHelpAction = new ContextHelpAction();
124 private JPanel buttonsLeftPanel;
125 private JPanel buttonsRightPanel;
126 private JPanel mainPanel;
127 private JPanel topPanel;
128
129 protected boolean isOkay = false;
130
131 private JPanel bottomPanel;
132
133 private JSeparator buttonSeparator;
134
135 /***
136 * Constructs a dialog showing the specified buttons and a
137 * <code>mainComponent</code>.
138 * @param owner the dialog owner
139 * @param buttons the ored value of button codes,
140 * e.g BUTTON_CANCEL | BUTTON_OKAY
141 * @param mainComponent the main component of the dialog, shown
142 * in the center of the dialog.
143 */
144 public DefaultDialog(Dialog owner, int buttons, JComponent mainComponent)
145 {
146 super(owner);
147
148 initialize(buttons, mainComponent);
149 }
150
151 /***
152 * Constructs a dialog showing the specified buttons and a
153 * <code>mainComponent</code>.
154 * @param owner the dialog owner
155 * @param buttons the ored value of button codes,
156 * e.g BUTTON_CANCEL | BUTTON_OKAY
157 * @param mainComponent the main component of the dialog, shown
158 * in the center of the dialog.
159 */
160 public DefaultDialog(Frame owner, int buttons, JComponent mainComponent)
161 {
162 super(owner);
163
164 initialize(buttons, mainComponent);
165 }
166
167 /***
168 * Constructs a dialog. The escape key is by default bound to the cancel
169 * button.
170 *
171 * @param buttons a logical and combination of button type constants
172 * @param mainComponent the main component that is centered in the dialog
173 */
174 public DefaultDialog(int buttons, JComponent mainComponent)
175 {
176 initialize(buttons, mainComponent);
177 }
178
179 /***
180 * Convenience wrapper for
181 * {@link DefaultDialog#DefaultDialog(Dialog, int, JComponent)
182 * DefaultDialog(Dialog, int, null)}.
183 */
184 public DefaultDialog(Dialog owner, int buttons)
185 {
186 this(owner, buttons, null);
187 }
188
189 /***
190 * Convenience wrapper for
191 * {@link DefaultDialog#DefaultDialog(Frame, int, JComponent)
192 * DefaultDialog(Frame, int, null)}.
193 */
194 public DefaultDialog(Frame owner, int buttons)
195 {
196 this(owner, buttons, null);
197 }
198
199 /***
200 * Convenience wrapper for
201 * {@link DefaultDialog#DefaultDialog(int, JComponent)
202 * DefaultDialog(int, null)}.
203 * @param buttons
204 */
205 public DefaultDialog(int buttons)
206 {
207 this(buttons, null);
208 }
209
210 /***
211 * Convenience wrapper for
212 * {@link DefaultDialog#DefaultDialog(Dialog, int)
213 * DefaultDialog(Dialog, BUTTON_OKAY | BUTTON_CANCEL)}.
214 */
215 public DefaultDialog(Dialog owner)
216 {
217 this(owner, BUTTON_OKAY | BUTTON_CANCEL);
218 }
219
220 /***
221 * Convenience wrapper for
222 * {@link DefaultDialog#DefaultDialog(Frame, int)
223 * DefaultDialog(Frame, BUTTON_OKAY | BUTTON_CANCEL)}.
224 */
225 public DefaultDialog(Frame owner)
226 {
227 this(owner, BUTTON_OKAY | BUTTON_CANCEL);
228 }
229
230 /***
231 * Constructs a dialog with an okay and cancel button.
232 */
233 public DefaultDialog()
234 {
235 this(BUTTON_OKAY | BUTTON_CANCEL);
236 }
237
238 private void initialize(int buttons, JComponent mainComponent) {
239
240 topPanel = new JPanel(new BorderLayout());
241 topPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
242
243 mainPanel = new JPanel();
244 topPanel.add(mainPanel, BorderLayout.CENTER);
245 if (mainComponent != null) {
246 setMainComponent(mainComponent);
247 }
248
249 buttonsLeftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
250 if ((buttons & BUTTON_HELP) > 0) {
251 buttonsLeftPanel.add(new JButton(helpAction));
252 }
253
254 if ((buttons & BUTTON_CONTEXT_HELP) > 0) {
255 buttonsLeftPanel.add(new JButton(contextHelpAction));
256 }
257
258 buttonsRightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
259 if ((buttons & BUTTON_OKAY) > 0) {
260 JButton jbOkay = new JButton(okayAction);
261 buttonsRightPanel.add(jbOkay);
262 }
263 if ((buttons & BUTTON_CLOSE) > 0) {
264 buttonsRightPanel.add(new JButton(closeAction));
265 }
266 if ((buttons & BUTTON_DEFAULTS) > 0) {
267 buttonsRightPanel.add(new JButton(defaultsAction));
268 }
269 if ((buttons & BUTTON_APPLY) > 0) {
270 buttonsRightPanel.add(new JButton(applyAction));
271 }
272 if ((buttons & BUTTON_CANCEL) > 0) {
273 JButton jbCancel = new JButton(cancelAction);
274 GUIHelper.bindEscapeKey(topPanel, jbCancel.getAction());
275 buttonsRightPanel.add(jbCancel);
276 }
277
278
279
280
281 if (SystemHelper.IS_MACOSX) {
282 buttonsRightPanel.add(Box.createHorizontalStrut(15));
283 }
284
285 bottomPanel = new JPanel(new BorderLayout());
286 bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
287 bottomPanel.add(buttonsLeftPanel, BorderLayout.WEST);
288 bottomPanel.add(buttonsRightPanel, BorderLayout.EAST);
289 topPanel.add(bottomPanel, BorderLayout.SOUTH);
290
291 setButtonSeparatorVisible(true);
292
293 getContentPane().setLayout(new BorderLayout());
294 getContentPane().add(topPanel, BorderLayout.CENTER);
295
296 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
297 addWindowListener(new CloseListener());
298
299
300 if (mainComponent != null) {
301 pack();
302 }
303 }
304
305 /***
306 * Called by ApplyAction and OkayAction when the dialog is closed.
307 *
308 * @return true, if apply was successful and close() should be
309 * invoked; false, otherwise
310 */
311 public boolean apply()
312 {
313 return true;
314 }
315
316 protected void cancelled()
317 {
318 }
319
320 protected void defaults()
321 {
322 }
323
324 /***
325 * Disposes the dialog. Called by the actions to close the dialog. Sub
326 * classes can reimplement this.
327 *
328 * @see #isOkay()
329 */
330 public void close()
331 {
332 dispose();
333 }
334
335 /***
336 * Returns the button panel. Sub classes can add aditional buttons to this
337 * panel.
338 */
339 public JPanel getButtonPanel()
340 {
341 return buttonsRightPanel;
342 }
343
344 /***
345 * Returns the apply action.
346 */
347 public Action getApplyAction()
348 {
349 return applyAction;
350 }
351
352 /***
353 * Returns the cancel action.
354 */
355 public Action getCancelAction()
356 {
357 return cancelAction;
358 }
359
360 /***
361 * Returns the defaults action.
362 */
363 public Action getDefaultsAction()
364 {
365 return defaultsAction;
366 }
367
368 /***
369 * Returns the cancel action.
370 */
371 public Action getOkayAction()
372 {
373 return okayAction;
374 }
375
376 /***
377 * Returns the cancel action.
378 */
379 public Action getHelpAction()
380 {
381 return helpAction;
382 }
383
384 /***
385 * Returns the cancel action.
386 */
387 public Action getContextHelpAction()
388 {
389 return contextHelpAction;
390 }
391
392 /***
393 * Returns the close action.
394 */
395 public Action getCloseAction()
396 {
397 return closeAction;
398 }
399
400 /***
401 * Invoked when the context help button is selected.
402 *
403 * <p>
404 * Invokes {@link WhatsThis#enterWhatsThisMode()}. Sub-classes may override
405 * this method.
406 */
407 public void contextHelp()
408 {
409 WhatsThis.enterWhatsThisMode();
410 }
411
412 /***
413 * Invoked when the context help is selected.
414 *
415 * <p>
416 * Does nothing. Sub-classes may override this method.
417 */
418 public void help()
419 {
420 }
421
422 public void setApplyOnEnter(boolean apply)
423 {
424 if (apply) {
425 GUIHelper.bindEnterKey(topPanel, getOkayAction());
426 }
427 else {
428
429 topPanel.getActionMap().remove(getOkayAction());
430 }
431 }
432
433 /***
434 * Sets the main component to <code>component</code>.
435 * TODO can this be called multiple times?, it can they are all added, don't
436 * know how this looks then
437 */
438 public void setMainComponent(Component component)
439 {
440 getMainPanel().setLayout(new BorderLayout());
441 getMainPanel().add(component, BorderLayout.CENTER);
442 }
443
444 /***
445 * Returns the main panel. Sub classes can add components to this panel.
446 */
447 public JPanel getMainPanel()
448 {
449 return mainPanel;
450 }
451
452 /***
453 * Returns the top most panel.
454 */
455 public JPanel getTopPanel()
456 {
457 return topPanel;
458 }
459
460 public void setButtonSeparatorVisible(boolean visible)
461 {
462 if (visible) {
463 if (buttonSeparator == null) {
464 buttonSeparator = new JSeparator();
465 bottomPanel.add(buttonSeparator, BorderLayout.NORTH);
466 }
467 }
468 else {
469 if (buttonSeparator != null) {
470 bottomPanel.remove(buttonSeparator);
471 buttonSeparator = null;
472 }
473 }
474 }
475
476 /***
477 * Returns true, if the dialog was closed with the Okay button.
478 */
479 public boolean isOkay()
480 {
481 return isOkay;
482 }
483
484
485
486
487
488
489
490
491
492 /***
493 * Sets this dialogs position relative to <code>c</code> and makes it
494 * visible.
495 */
496 public void show(Component c)
497 {
498 if (!isVisible()) {
499 if (c != null) {
500 setLocationRelativeTo(c);
501 }
502 setVisible(true);
503 }
504 else {
505 toFront();
506 }
507 }
508
509 /***
510 * Handles the apply button. Invokes <code>apply()</code> when pressed.
511 *
512 * @see #apply()
513 */
514 private class ApplyAction extends AbstractAction {
515
516 public ApplyAction()
517 {
518 putValue(Action.NAME, i18n.tr("Apply"));
519 putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
520 }
521
522 public void actionPerformed(ActionEvent event)
523 {
524 apply();
525 }
526
527 }
528
529 /***
530 * Handles the Cancel button. Invokes <code>close()</code> when pressed.
531 *
532 * @see #close()
533 */
534 private class CancelAction extends AbstractAction {
535
536 public CancelAction()
537 {
538 putValue(Action.NAME, i18n.tr("Cancel"));
539 putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_C));
540 }
541
542 public void actionPerformed(ActionEvent event)
543 {
544 isOkay = false;
545 cancelled();
546 close();
547 }
548
549 }
550
551 /***
552 * Handle the Close button. Invokes <code>close()</code> when pressed.
553 *
554 * @see #close()
555 */
556 private class CloseAction extends AbstractAction {
557
558 public CloseAction()
559 {
560 putValue(Action.NAME, i18n.tr("Close"));
561 putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
562 }
563
564 public void actionPerformed(ActionEvent event)
565 {
566 isOkay = false;
567 close();
568 }
569
570 }
571
572 /***
573 * Handles the Help button. Shows the help dialog registered for the
574 * <code>rootPane</code> of this dialog.
575 *
576 * It suffices to register a help id and a helpset for the dialog using
577 * {@link xnap.gui.util.HelpManager#enableHelpKeys(JComponent, String, HelpSet)} like this:
578 *
579 * <pre>
580 * HelpManager.enableHelpKeys(getRootPane(), "id-string", helpSet);
581 * </pre>
582 */
583 private class HelpAction extends AbstractAction
584 {
585
586 public HelpAction()
587 {
588 putValue(Action.NAME, i18n.tr("Help"));
589 putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_H));
590 }
591
592 public void actionPerformed(ActionEvent event)
593 {
594 help();
595 }
596
597 }
598
599 private class DefaultsAction extends AbstractAction
600 {
601
602 public DefaultsAction()
603 {
604 putValue(Action.NAME, i18n.tr("Defaults"));
605 putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_D));
606 }
607
608 public void actionPerformed(ActionEvent event)
609 {
610 defaults();
611 }
612
613 }
614
615 private class ContextHelpAction extends WhatsThisAction
616 {
617
618 public void actionPerformed(ActionEvent event)
619 {
620 contextHelp();
621 }
622
623 }
624
625
626 /***
627 * Handle the Okay button. Invokes <code>apply()</code> and
628 * <code>close()</code> when pressed.
629 *
630 * @see #apply()
631 * @see #close()
632 */
633 private class OkayAction extends AbstractAction {
634
635 public OkayAction()
636 {
637 putValue(Action.NAME, i18n.tr("OK"));
638 putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
639 }
640
641 public void actionPerformed(ActionEvent event)
642 {
643 if (!apply()) {
644 return;
645 }
646
647 isOkay = true;
648 close();
649 }
650
651 }
652
653 private class CloseListener extends WindowAdapter {
654
655 public void windowClosing (java.awt.event.WindowEvent evt)
656 {
657 isOkay = false;
658 close();
659 }
660 }
661
662 /***
663 * Tries to enforce a minimum size for a dialog.
664 *
665 * <p>This class is not used, since it does not work: The events are thrown
666 * while the resize is in progress causing calls to setSize() to get lost.
667 * Can we come up with a working solution?
668 *
669 * @author Steffen Pingel
670 */
671 class ResizeListener extends ComponentAdapter {
672
673 public void componentResized(ComponentEvent event)
674 {
675 Dimension minimumSize = getMinimumSize();
676 if (minimumSize == null
677 || (minimumSize.width <= getWidth()
678 && minimumSize.height <= getHeight())) {
679 return;
680 }
681
682 Dimension newSize = getSize();
683 if (getWidth() < minimumSize.width) {
684 newSize.width = minimumSize.width;
685 }
686 if (getHeight() < minimumSize.height) {
687 newSize.height = minimumSize.height;
688 }
689 setSize(newSize);
690 }
691
692 }
693
694
695 }