View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.gui.component;
21  
22  import java.awt.*;
23  import java.awt.Component;
24  import java.awt.FlowLayout;
25  import java.awt.event.*;
26  import java.awt.event.WindowAdapter;
27  
28  import javax.help.BadIDException;
29  import javax.help.CSH;
30  import javax.help.HelpBroker;
31  import javax.help.HelpSet;
32  import javax.help.InvalidHelpSetContextException;
33  import javax.help.Map;
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.JOptionPane;
42  import javax.swing.JPanel;
43  import javax.swing.JSeparator;
44  import javax.swing.border.EmptyBorder;
45  
46  import org.apache.log4j.Logger;
47  import org.xnap.XNap;
48  import org.xnap.gui.action.*;
49  import org.xnap.gui.component.*;
50  import org.xnap.gui.util.GUIHelper;
51  import org.xnap.gui.util.HelpManager;
52  import org.xnap.util.SystemHelper;
53  
54  
55  /***
56   * This class provides a default implementation for a dialog. A dialog consists
57   * of two areas. The main area, located at the center of the dialog, contains
58   * the user interaction components. The button area, located at the south
59   * west of the dialog, is surrounded by an empty border and contains the 
60   * buttons.
61   *
62   * <p>The most common buttons are provided with default actions.</p>
63   *
64   * <p>If you extend this class make sure <code>pack()</code> is called after
65   * all components have been added.</p>
66   */
67  public class DefaultDialog extends JDialog 
68  {
69  
70      //--- Constant(s) ---
71      
72      /***
73       * The button type for no buttons.
74       */
75      public static int BUTTON_NONE = 0;
76  
77      /***
78       * The button type for the okay button.
79       */
80      public static int BUTTON_OKAY = 1;
81  
82      /***
83       * The button type for the apply button.
84       */
85      public static int BUTTON_APPLY = 2;
86  
87      /***
88       * The button type for the cancel button.
89       */
90      public static int BUTTON_CANCEL = 4;
91  
92      /***
93       * The button type for the close button.
94       */
95      public static int BUTTON_CLOSE = 8;
96  
97      /***
98       * The button type for the help button.
99       */
100     public static int BUTTON_HELP = 16;
101 
102 	/***
103 	 * The button type for the context help button.
104 	 */
105 	public static int BUTTON_CONTEXT_HELP = 32;
106 
107     //--- Data field(s) ---
108 
109     private static Logger logger = Logger.getLogger(DefaultDialog.class);
110 
111     private ApplyAction acApply = new ApplyAction();
112     private CancelAction acCancel = new CancelAction();
113     private CloseAction acClose = new CloseAction();
114     private OkayAction acOkay = new OkayAction();
115     private HelpAction acHelp = new HelpAction();
116     private JPanel jpButtonsLeft;
117     private JPanel jpButtonsRight;
118     private JPanel jpMain;
119 	private JPanel topPanel;
120 
121     protected boolean isOkay = false;
122 
123     //--- Constructor(s) ---
124 
125     /***
126      * Constructs a dialog. The escape key is by default bound to the cancel
127      * button.
128      *
129      * @param buttons a logical and combination of button type constants
130      * @param component the main component that is centered in the dialog
131      * @param exitOnEnter execute the okay action when the user presses enter
132      */
133     public DefaultDialog(int buttons, JComponent component, 
134 						 boolean exitOnEnter)
135     {
136 		// the top most panel
137 		topPanel = new JPanel(new BorderLayout());
138 		topPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
139 
140 		jpMain = new JPanel();
141 		topPanel.add(jpMain, BorderLayout.CENTER);
142 		if (component != null) {
143 			setMainComponent(component);
144 		}
145 
146 		jpButtonsLeft = new JPanel(new FlowLayout(FlowLayout.LEFT));
147 		if ((buttons & BUTTON_HELP) > 0) {
148 			jpButtonsLeft.add(new JButton(acHelp));
149 		}
150 
151 		if ((buttons & BUTTON_CONTEXT_HELP) > 0) {
152 			jpButtonsLeft.add(new XNapButton(new ContextHelpAction()));
153 		}
154 
155 		jpButtonsRight = new JPanel(new FlowLayout(FlowLayout.RIGHT));
156 		if ((buttons & BUTTON_OKAY) > 0) {
157 			JButton jbOkay = new JButton(acOkay);
158 			if (exitOnEnter) {
159 				GUIHelper.bindEnterKey(topPanel, jbOkay.getAction());
160 			}
161 			jpButtonsRight.add(jbOkay);
162 		}
163 		if ((buttons & BUTTON_CLOSE) > 0) {
164 			jpButtonsRight.add(new JButton(acClose));
165 		}
166 		if ((buttons & BUTTON_APPLY) > 0) {
167 			jpButtonsRight.add(new JButton(acApply));
168 		}
169 		if ((buttons & BUTTON_CANCEL) > 0) {
170 			JButton jbCancel = new JButton(acCancel);
171 			GUIHelper.bindEscapeKey(topPanel, jbCancel.getAction());
172 			jpButtonsRight.add(jbCancel);
173 		}
174 
175 		/* the aqua look and feel adds a small resize control in the lower
176 		   right corner which overlaps the status panel therefore we add a
177 		   little offset.  */
178 		if (SystemHelper.isMacOSX) {
179 			jpButtonsRight.add(Box.createHorizontalStrut(15));
180 		}
181 
182 		// separator line
183 		JSeparator js = new JSeparator();
184 
185 		JPanel jpButtons = new JPanel(new BorderLayout());
186 		jpButtons.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
187 		jpButtons.add(js, BorderLayout.NORTH);
188 		jpButtons.add(jpButtonsLeft, BorderLayout.WEST);
189 		jpButtons.add(jpButtonsRight, BorderLayout.EAST);
190 		topPanel.add(jpButtons, BorderLayout.SOUTH);
191 
192 		getContentPane().setLayout(new BorderLayout());
193 		getContentPane().add(topPanel, BorderLayout.CENTER);
194 
195 		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
196         addWindowListener(new CloseListener());
197 
198 		if (component != null) {
199 			pack();
200 		}
201     }
202 
203     public DefaultDialog(int buttons, JComponent component)
204     {
205 		this(buttons, component, true);
206     }
207     
208     public DefaultDialog(int buttons, boolean exitOnEnter)
209     {
210 		this(buttons, null, exitOnEnter);
211     }
212 
213     public DefaultDialog(int buttons)
214     {
215 		this(buttons, null);
216     }
217 
218     /***
219      * Constructs a dialog with an okay and cancel button.
220      */
221     public DefaultDialog()
222     {
223 		this(BUTTON_OKAY | BUTTON_CANCEL);
224     }
225 
226     //--- Method(s) ---
227 
228     /***
229      * Called by ApplyAction and OkayAction when the dialog is closed.
230      * 
231 	 * @return true, if apply was successful and close() should be
232 	 *         invoked; false, otherwise
233      * @exception IllegalArgumentException
234      */
235     public boolean apply()
236     {
237 		return true;
238     }
239 
240     /***
241      * Disposes the dialog. Called by the actions to close the dialog. Sub
242      * classes can reimplement this. 
243      *
244      * @see #isOkay()
245      */
246     public void close()
247     {
248 		dispose();
249     }
250 
251     /***
252      * Returns the button panel. Sub classes can add aditional buttons to this
253      * panel.
254      */
255     public JPanel getButtonPanel()
256     {
257 		return jpButtonsRight;
258     }
259 
260     /***
261      * Returns the cancel action.
262      */
263     public Action getCancelAction()
264     {
265 		return acCancel;
266     }
267 
268 	/***
269 	 * Returns the cancel action.
270 	 */
271 	public Action getOkayAction()
272 	{
273 		return acOkay;
274 	}
275 
276     /***
277      * Returns the close action.
278      */
279     public Action getCloseAction()
280     {
281 		return acClose;
282     }
283 
284     /***
285      * Sets the main component to <code>component</code>.
286      */
287     public void setMainComponent(Component component)
288     {
289 		getMainPanel().setLayout(new BorderLayout());
290 		getMainPanel().add(component, BorderLayout.CENTER);
291     }
292     
293     /***
294      * Returns the main panel. Sub classes can add components to this panel.
295      */
296     public JPanel getMainPanel()
297     {
298 		return jpMain;
299     }
300 
301 	/***
302 	 * Returns the top most panel.
303 	 */
304 	public JPanel getTopPanel()
305 	{
306 		return topPanel;
307 	}
308 
309     /***
310      * Returns true, if the dialog was closed with the Okay button.
311      */
312     public boolean isOkay()
313     {
314 		return isOkay;
315     }
316 
317     /***
318      * Sets this dialogs position relative to <code>c</code> and makes it 
319      * visible.
320      */
321     public void show(Component c)
322     {
323 		if (!isVisible()) {
324 			if (c != null) {
325 				setLocationRelativeTo(c);
326 			}
327 			setVisible(true);
328 		}
329 		else {
330 			toFront();
331 		}
332     }
333 
334     /***
335      * Handles the apply button. Invokes <code>apply()</code> when pressed.
336      * 
337      * @see #apply()
338      */
339     private class ApplyAction extends AbstractAction {
340 
341         public ApplyAction() 
342 		{
343 			putValue(Action.NAME, XNap.tr("Apply"));
344 			putValue(Action.SHORT_DESCRIPTION, XNap.tr("Applies changes."));
345 			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
346 		}
347 	
348         public void actionPerformed(ActionEvent event) 
349 		{
350 			try {
351 				apply();
352 			}
353 			catch (IllegalArgumentException e) {
354 				JOptionPane.showMessageDialog
355 					(DefaultDialog.this, e.getMessage(), 
356 					 XNap.tr("Illegal Value"), JOptionPane.ERROR_MESSAGE);
357 			}
358         }
359 	
360     }
361 
362     /***
363      * Handles the Cancel button. Invokes <code>close()</code> when pressed.
364      * 
365      * @see #close()
366      */
367     private class CancelAction extends AbstractAction {
368 
369         public CancelAction()
370 		{
371             putValue(Action.NAME, XNap.tr("Cancel"));
372             putValue(Action.SHORT_DESCRIPTION, 
373 					 XNap.tr("Closes the dialog without saving changes."));
374 			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_C));
375 		}
376 
377         public void actionPerformed(ActionEvent event) 
378 		{
379 			isOkay = false;
380 			close();
381 		}
382 	
383     }
384 
385     /***
386      * Handle the Close button. Invokes <code>close()</code> when pressed.
387      * 
388      * @see #close()
389      */
390     private class CloseAction extends AbstractAction {
391 
392         public CloseAction() 
393 		{
394 			putValue(Action.NAME, XNap.tr("Close"));
395 			putValue(Action.SHORT_DESCRIPTION, XNap.tr("Closes the dialog."));
396 			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
397 		}
398 	
399         public void actionPerformed(ActionEvent event) 
400 		{
401 			isOkay = false;
402 			close();
403         }
404 	
405     }
406 
407     /***
408      * Handles the Help button. Shows the help dialog registered for the
409      * <code>rootPane</code> of this dialog.
410      *
411      * It suffices to register a help id and a helpset for the dialog using
412      * {@link xnap.gui.util.HelpManager#enableHelpKeys(JComponent, String, HelpSet)} like this:
413      *
414      * <pre>
415      *  HelpManager.enableHelpKeys(getRootPane(), "id-string", helpSet);
416      * </pre>
417      */
418     private class HelpAction extends AbstractAction
419     {
420 
421         public HelpAction() 
422 		{
423 			putValue(Action.NAME, XNap.tr("Help"));
424 			putValue(Action.SHORT_DESCRIPTION, 
425 					 XNap.tr("Shows the help dialog."));
426 			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_H));
427 			if (HelpManager.getMainHelpSet() == null) {
428 				this.setEnabled(false);
429 			}
430 		}
431 	
432         public void actionPerformed(ActionEvent event) 
433 		{
434 			HelpBroker hb = HelpManager.getMainHelpBroker();
435 			String id = CSH.getHelpIDString(getRootPane());
436 			HelpSet hs = CSH.getHelpSet(getRootPane());
437 	    
438 			if (hs == null) {
439 				hs = hb.getHelpSet();
440 			}
441 	    
442 			try {
443 				Map.ID mapId = Map.ID.create(id, hs);
444 				if (mapId == null) {
445 					mapId = hs.getHomeID();
446 				}
447 				hb.setCurrentID(mapId);
448 				hb.setDisplayed(true);
449 			}
450 			catch (BadIDException be) {
451 				logger.debug("bad id", be);
452 			}
453 			catch (InvalidHelpSetContextException e) {
454 				logger.debug("helpset", e);
455 			}
456         }
457 	
458     }
459 
460     /***
461      * Handle the Okay button. Invokes <code>apply()</code> and
462      * <code>close()</code> when pressed.
463      * 
464      * @see #apply()
465      * @see #close()
466      */
467     private class OkayAction extends AbstractAction {
468 
469         public OkayAction() 
470 		{
471 			putValue(Action.NAME, XNap.tr("OK"));
472 			putValue(Action.SHORT_DESCRIPTION, 
473 					 XNap.tr("Closes the dialog saving changes."));
474 			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
475 		}
476 	
477         public void actionPerformed(ActionEvent event) 
478 		{
479 			try {
480 				if (!apply()) {
481 					return;
482 				}
483 			}
484 			catch (IllegalArgumentException e) {
485 				JOptionPane.showMessageDialog
486 					(DefaultDialog.this, e.getLocalizedMessage(), 
487 					 XNap.tr("Illegal Value"), JOptionPane.ERROR_MESSAGE);
488 				return;
489 			}
490 
491 			isOkay = true;
492 			close();
493         }
494 	
495     }
496 
497     private class CloseListener extends WindowAdapter
498     {
499 		public void windowClosing (java.awt.event.WindowEvent evt) 
500 		{
501 			isOkay = false;
502 			close();
503 		}
504     }
505 
506 }