View Javadoc

1   /*
2    *  XNap Commons
3    *
4    *  Copyright (C) 2005  Felix Berger
5    *  Copyright (C) 2005  Steffen Pingel
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2.1 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free Software
19   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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  	// TODO can we make this enums, please, please? ;-)
66  	
67  	/* Yeah, but how would we implement that?
68   
69  	public static enum Buttons { None, Okay, Cancel }
70  	static {
71  		Buttons[] button = new Buttons[] { Buttons.Okay, Buttons.Cancel }; 
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 		// the top most panel
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 		/* the aqua look and feel adds a small resize control in the lower
279 		   right corner which overlaps the status panel therefore we add a
280 		   little offset.  */
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 		/*addComponentListener(new ResizeListener());*/
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     		// TODO depends on the GUIHelper implementation, move this code there
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 	public void pack() {
486 		setMinimumSize(null);
487 		super.pack();
488 		setMinimumSize(getSize());
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 }