001: /*
002: * Copyright (c) 2004 JETA Software, Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without modification,
005: * are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JETA Software nor the names of its contributors may
015: * be used to endorse or promote products derived from this software without
016: * specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
021: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
022: * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
023: * INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
024: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
025: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026: * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: */
029:
030: package com.jeta.open.gui.framework;
031:
032: import java.awt.BorderLayout;
033: import java.awt.Component;
034: import java.awt.Container;
035: import java.awt.Dialog;
036: import java.awt.Dimension;
037: import java.awt.Font;
038: import java.awt.Frame;
039: import java.awt.event.ActionEvent;
040: import java.awt.event.ActionListener;
041: import java.awt.event.KeyEvent;
042: import java.awt.event.WindowAdapter;
043: import java.awt.event.WindowEvent;
044: import java.util.Iterator;
045: import java.util.LinkedList;
046:
047: import javax.swing.AbstractAction;
048: import javax.swing.JButton;
049: import javax.swing.JComponent;
050: import javax.swing.JDialog;
051: import javax.swing.JOptionPane;
052: import javax.swing.JPanel;
053: import javax.swing.KeyStroke;
054:
055: import com.jeta.open.gui.utils.JETAToolbox;
056: import com.jeta.open.i18n.I18N;
057: import com.jeta.open.rules.JETARule;
058: import com.jeta.open.rules.RuleResult;
059:
060: /**
061: * This is a base class for all dialogs in the system. It provides a skeleton
062: * dialog box with a cancel and ok button. Callers can add their own containers
063: * to this dialog's content panel. This class also supports validators for the
064: * dialog. This allows the dialog to validate the input and if validation fails,
065: * maintain the dialog on the screen. See {@link com.jeta.open.rules.JETARule}.
066: *
067: * @author Jeff Tassin
068: */
069: public class JETADialog extends JDialog {
070: private JPanel m_contentpane; // this is the container that callers add
071: // their controls to
072:
073: private CommandListener m_cmdListener = new CommandListener(this );
074: private boolean m_bOk = false;
075:
076: /**
077: * The panel at the bottom of the dialog that contains the Ok and Cancel
078: * buttons.
079: */
080: private JPanel m_btnPanel;
081: private JButton m_okbtn;
082: private JButton m_closebtn;
083: private JButton m_helpbtn;
084:
085: private JETAController m_controller;
086:
087: /**
088: * a list of JETARules that are responsible for handling validation for the
089: * dialog
090: */
091: private LinkedList m_validators;
092:
093: /**
094: * The component that has initial focus
095: */
096: private JComponent m_initialFocusComponent;
097:
098: /**
099: * The main panel for this dialog.
100: */
101: private JComponent m_primaryPanel;
102:
103: /** this is a list of to JETADialogListener objects */
104: private LinkedList m_listeners;
105:
106: /** for ui director updates */
107: private javax.swing.Timer m_timer;
108:
109: /**
110: * The name of the button panel.
111: */
112: public static final String ID_BUTTON_PANEL = "button.panel";
113: /**
114: * The names of the ok and cancel buttons.
115: */
116: public static final String ID_OK = "JETADialog.ok";
117: public static final String ID_CANCEL = "JETADialog.cancel";
118:
119: /**
120: * ctor
121: */
122: public JETADialog(Dialog owner, boolean bModal) {
123: super (owner, bModal);
124: _initialize();
125: }
126:
127: /**
128: * ctor
129: */
130: public JETADialog(Frame owner, boolean bModal) {
131: super (owner, bModal);
132: _initialize();
133: }
134:
135: /**
136: * Forwarded from the view when an action occurs.
137: *
138: * @param commandId
139: * the name of the action associated with the event
140: * @param evt
141: * the action event
142: * @return true if the controller handled the action
143: */
144: public boolean actionPerformed(String commandId, ActionEvent evt) {
145: boolean bresult = false;
146: JETAController controller = getController();
147: if (controller != null)
148: bresult = controller.actionPerformed(commandId, evt);
149:
150: if (!bresult) {
151: String actionCommand = evt.getActionCommand();
152: if (actionCommand.equals(ID_OK)) {
153: cmdOk();
154: } else if (actionCommand.equals(ID_CANCEL)) {
155: cmdCancel();
156: }
157: }
158: return true;
159: }
160:
161: /**
162: * Adds a controller to the list of controllers that can handle events for
163: * this dialog. When an menu or toolbar event occurs, the event is routed to
164: * each controller added to this frame. Once a controller is found that
165: * handles the event, the event routing is considered complete and no other
166: * controllers are evaluated.
167: *
168: * @param controller
169: * the controller to add
170: */
171: public void addController(JETAController controller) {
172: assert (false);
173: }
174:
175: /**
176: * Adds a listener to the list of listeners for this dialog
177: */
178: public void addDialogListener(JETADialogListener listener) {
179: if (m_listeners == null)
180: m_listeners = new LinkedList();
181:
182: assert (listener != null);
183: m_listeners.add(listener);
184: }
185:
186: /**
187: * This adds a rule that will be used to validate the user input for this
188: * dialog. The validate method is called on each of these rules when the
189: * user hits the ok button. If any rule fails validation, an error message
190: * appears and the dialog will not close.
191: */
192: public void addValidator(JETARule validator) {
193: addValidator(null, validator);
194: }
195:
196: /**
197: * This adds a rule that will be used to validate the user input for this
198: * dialog. The validate method is called on each of these validators when
199: * the user hits the ok button. If any rule fails validation, an error
200: * message appears and the dialog will not close.
201: *
202: * @param parameter
203: * a parameter to pass to the validator. If this object is an
204: * Object[] type, then it is passed directly to the validator.
205: * @param validator
206: * the rule to add to this dialog.
207: */
208: public void addValidator(Object parameter, JETARule validator) {
209: if (validator == null)
210: return;
211:
212: if (m_validators == null)
213: m_validators = new LinkedList();
214:
215: if (parameter == null) {
216: m_validators
217: .add(new ValidatorRule(validator, new Object[0]));
218: } else if (parameter instanceof Object[]) {
219: m_validators.add(new ValidatorRule(validator,
220: (Object[]) parameter));
221: } else {
222: Object[] params = new Object[1];
223: params[0] = parameter;
224: m_validators.add(new ValidatorRule(validator, params));
225: }
226: }
227:
228: /**
229: * Closes the dialog
230: */
231: public void cmdCancel() {
232: setOk(false);
233: dispose();
234: }
235:
236: /**
237: * Close the dialog and set the ok flag
238: */
239: public void cmdOk() {
240: if (validateValidators() && validateListeners()) {
241: setOk(true);
242: dispose();
243: }
244: }
245:
246: /**
247: * Creates the button panel at the bottom of the dialog that contains the
248: * 'ok' and 'close' buttons.
249: *
250: * @return the panel that contains 'ok' and 'close' btns.
251: */
252: private JPanel createButtonPanel() {
253: JETAPanel btnpanel = new JETAPanel(new java.awt.FlowLayout(
254: java.awt.FlowLayout.RIGHT));
255: m_okbtn = new JButton(I18N.getLocalizedMessage("Ok"));
256: m_closebtn = new JButton(I18N.getLocalizedMessage("Cancel"));
257: m_helpbtn = new JButton(I18N.getLocalizedMessage("Help"));
258: m_helpbtn.setVisible(false);
259: btnpanel.add(m_helpbtn);
260: btnpanel.add(m_okbtn);
261: btnpanel.add(m_closebtn);
262: m_btnPanel = btnpanel;
263: m_btnPanel.setName(ID_BUTTON_PANEL);
264:
265: m_okbtn.setActionCommand(ID_OK);
266: m_okbtn.setName(ID_OK);
267: m_okbtn.addActionListener(m_cmdListener);
268: m_closebtn.setActionCommand(ID_CANCEL);
269: m_closebtn.setName(ID_CANCEL);
270: m_closebtn.addActionListener(m_cmdListener);
271: return m_btnPanel;
272: }
273:
274: /**
275: * Dispose the dialog
276: */
277: public void dispose() {
278: super .dispose();
279: JETAComponentCleanser cleanser = new JETAComponentCleanser();
280: cleanser.cleanse(this );
281:
282: m_timer.stop();
283: ActionListener[] als = (ActionListener[]) (m_timer
284: .getListeners(ActionListener.class));
285: if (als != null) {
286: for (int index = 0; index < als.length; index++) {
287: m_timer.removeActionListener(als[index]);
288: }
289: }
290:
291: m_cmdListener = null;
292: m_controller = null;
293:
294: if (m_validators != null)
295: m_validators.clear();
296:
297: m_initialFocusComponent = null;
298:
299: if (m_listeners != null)
300: m_listeners.clear();
301:
302: if (m_contentpane != null) {
303: m_contentpane.removeAll();
304: m_contentpane = null;
305: }
306: m_primaryPanel = null;
307: }
308:
309: /**
310: * @return the panel at the bottom of this dialog that contains the ok and
311: * cancel buttons
312: */
313: public JPanel getButtonPanel() {
314: return m_btnPanel;
315: }
316:
317: /**
318: * @return the close button for this dialog
319: */
320: public JButton getCloseButton() {
321: return m_closebtn;
322: }
323:
324: /**
325: * @return the controller for this dialog
326: */
327: public JETAController getController() {
328: return m_controller;
329: }
330:
331: /**
332: * @return the content container for this dialog. Callers add their controls
333: * to this container.
334: */
335: public Container getDialogContentPanel() {
336: return m_contentpane;
337: }
338:
339: /**
340: * @return the Help button for this dialog Normally, this button is not
341: * visible.
342: */
343: public JButton getHelpButton() {
344: return m_helpbtn;
345: }
346:
347: /**
348: * @return the Ok button for this dialog
349: */
350: public JButton getOkButton() {
351: return m_okbtn;
352: }
353:
354: /**
355: * Returns the preferred size for this dialog. This includes the title bar
356: * height, border height,width, and ok/cancel button heights. This dialog
357: * also gets the preferred size of the content panel, so you need to
358: * correctly set the preferred size in any content panel you set for this
359: * dialog.
360: *
361: * @return the preferred size for this dialog so that the caller can size
362: * the window properly
363: */
364: public Dimension getPreferredSize() {
365: return getPreferredSize(getDialogContentPanel()
366: .getPreferredSize());
367: }
368:
369: /**
370: * Returns the preferred size for this dialog. The size is calculated
371: * assuming the that dimensions of the main content panel are passed in by
372: * the caller. The content panel dimensions are added to the title bar
373: * height, border height,width, and ok/cancel button heights.
374: *
375: * @return the preferred size for this dialog so that the caller can size
376: * the window properly
377: */
378: protected Dimension getPreferredSize(Dimension contentDims) {
379: Dimension dbtn = m_btnPanel.getPreferredSize(); // add height of
380: // ok/cancel button
381: // panel
382: Dimension d = new Dimension(contentDims);
383: d.height += dbtn.height; // add height of ok/cancel button panel
384: d.height += JETAToolbox.getTitleBarHeight(); // add height of title
385: // bar
386: d.height += JETAToolbox.getFrameBorderThickness(); // add height of
387: // frame border
388: d.width += 2 * JETAToolbox.getFrameBorderThickness(); // add width of
389: // border
390: /**
391: * @JMT - temporary hack until I figure out how to read the frame border
392: * thickness
393: */
394: if (JETAToolbox.isOSX()) {
395: d.width -= 6;
396: }
397: return d;
398: }
399:
400: /**
401: * @return the one and only panel for this dialog. The panel must have been
402: * set previously by calling setPrimaryPanel.
403: */
404: public Component getPrimaryPanel() {
405: return m_primaryPanel;
406: }
407:
408: /**
409: * Initializes the components on this dialog
410: */
411: protected void _initialize() {
412: Container container = getContentPane();
413: container.setLayout(new BorderLayout());
414:
415: setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
416:
417: m_contentpane = new JPanel(new BorderLayout());
418: container.add(m_contentpane, BorderLayout.CENTER);
419: container.add(createButtonPanel(), BorderLayout.SOUTH);
420:
421: addWindowListener(new WindowAdapter() {
422: public void windowClosing(WindowEvent we) {
423: cmdCancel();
424: }
425:
426: public void windowOpened(WindowEvent we) {
427: if (m_initialFocusComponent != null)
428: m_initialFocusComponent.requestFocus();
429: }
430: });
431:
432: /**
433: * Start a timer that updates the controls of the active window every
434: * second
435: */
436: ActionListener uiupdater = new ActionListener() {
437: public void actionPerformed(ActionEvent evt) {
438: updateComponents(evt);
439: }
440: };
441:
442: KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0,
443: true);
444: getRootPane().getInputMap().put(ks, "CloseAction");
445: getRootPane().getActionMap().put("CloseAction",
446: new AbstractAction() {
447: public void actionPerformed(ActionEvent ae) {
448: cmdCancel();
449: }
450: });
451:
452: ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true);
453: getRootPane().getInputMap().put(ks, "EnterAction");
454: getRootPane().getActionMap().put("EnterAction",
455: new AbstractAction() {
456: public void actionPerformed(ActionEvent ae) {
457: cmdOk();
458: }
459: });
460:
461: int delay = 500; // milliseconds
462: m_timer = new javax.swing.Timer(delay, uiupdater);
463: m_timer.start();
464: }
465:
466: /**
467: * @return true if the user clicked the Ok button to close the dialog.
468: */
469: public boolean isOk() {
470: return m_bOk;
471: }
472:
473: /**
474: * Removes all registered controllers added to this dialog
475: */
476: public void removeAllControllers() {
477:
478: }
479:
480: /**
481: * Shows/Hides the button panel on this dialog.
482: */
483: public void setButtonPanelVisible(boolean bvis) {
484: assert (bvis == false);
485: Container container = getContentPane();
486: container.remove(m_btnPanel);
487: }
488:
489: /**
490: * Enables/Disables the cancel command
491: */
492: public void setCancelEnabled(boolean bCancel) {
493: if (!bCancel) {
494: m_closebtn.setEnabled(false);
495: }
496: }
497:
498: /**
499: * Sets the text for the close button
500: */
501: public void setCloseText(String txt) {
502: m_closebtn.setText(txt);
503: }
504:
505: /**
506: * Sets the font for the ok and close buttons on this dialog
507: *
508: * @param f
509: * the new font to set
510: */
511: public void setFont(Font f) {
512: m_okbtn.setFont(f);
513: m_closebtn.setFont(f);
514: }
515:
516: /**
517: * Sets the one and only panel for this dialog
518: */
519: public void setPrimaryPanel(JComponent primaryPanel) {
520: if (m_primaryPanel != null) {
521: m_contentpane.remove(m_primaryPanel);
522: }
523: m_primaryPanel = primaryPanel;
524: m_contentpane.add(primaryPanel, BorderLayout.CENTER);
525: }
526:
527: /**
528: * Sets the controller that will handle events for this dialog
529: */
530: public void setController(JETAController controller) {
531: m_controller = controller;
532: if (controller instanceof JETARule)
533: addValidator((JETARule) controller);
534: }
535:
536: /**
537: * Sets the component in this frame that will have initial focus.
538: *
539: * @param comp
540: * the component that gets initial focus when dialog is displayed
541: */
542: public void setInitialFocusComponent(JComponent comp) {
543: if (comp == null)
544: return;
545:
546: m_initialFocusComponent = comp;
547: // just in case this is set after the window is visible
548: comp.requestFocus();
549: }
550:
551: /**
552: * Sets the Ok flag
553: */
554: protected void setOk(boolean bok) {
555: m_bOk = bok;
556: }
557:
558: /**
559: * Sets the text for the ok button
560: */
561: public void setOkText(String txt) {
562: m_okbtn.setText(txt);
563: }
564:
565: /**
566: * Sets the title to the frame
567: */
568: public void setTitle(String title) {
569: super .setTitle(title);
570: }
571:
572: /**
573: * Shows the dialog in the center of the screen
574: */
575: public void showCenter() {
576: com.jeta.open.gui.utils.JETAToolbox.centerWindow(this );
577: show();
578: }
579:
580: /**
581: * Shows/hides the ok button
582: */
583: public void showOkButton(boolean bvis) {
584: m_okbtn.setVisible(bvis);
585: }
586:
587: /**
588: * Updates the components in the dialog based on the model state.
589: */
590: public void updateComponents(java.util.EventObject evt) {
591: JETAController controller = getController();
592: if (controller == null) {
593: // try the controller on the primary panel
594: if (m_primaryPanel != null
595: && m_primaryPanel instanceof JETAPanel) {
596: controller = ((JETAPanel) m_primaryPanel)
597: .getController();
598: }
599: }
600:
601: if (controller != null) {
602: controller.updateComponents(evt);
603: }
604: }
605:
606: /**
607: * Iterates through the list of listeners and allows them to process the
608: * inputs. For example, the user may be inputing parameters for a databse.
609: * We would like to perform the database operation. If the operation fails,
610: * we would like the dialog to remain on the screen so the user can make any
611: * appropriate adjustments to the dialog data. This is the purpose of the
612: * JETADialogListeners.
613: */
614: protected boolean validateListeners() {
615: if (m_listeners != null) {
616: Iterator iter = m_listeners.iterator();
617: while (iter.hasNext()) {
618: JETADialogListener listener = (JETADialogListener) iter
619: .next();
620: if (!listener.cmdOk())
621: return false;
622: }
623: }
624: return true;
625: }
626:
627: /**
628: * Iterates through the list of controller validators and allows them to
629: * validate the input for this dialog. If any controller fails validation,
630: * then we show an error message and return false.
631: */
632: protected boolean validateValidators() {
633: boolean bresult = true;
634: if (m_validators != null) {
635: Iterator iter = m_validators.iterator();
636: while (iter.hasNext()) {
637: ValidatorRule validator = (ValidatorRule) iter.next();
638: if (validator != null) {
639: JETARule rule = validator.getRule();
640: RuleResult result = rule.check(validator
641: .getParameters());
642: if (result != null) {
643: if (result.getCode() == RuleResult.FAIL_MESSAGE_ID) {
644: String title = I18N
645: .getLocalizedMessage("Error");
646: JOptionPane.showMessageDialog(this , result
647: .getMessage(), title,
648: JOptionPane.ERROR_MESSAGE);
649: bresult = false;
650: break;
651: } else if (result.getCode() == RuleResult.FAIL_NO_MESSAGE_ID) {
652: bresult = false;
653: break;
654: }
655: }
656: }
657: }
658: }
659: return bresult;
660: }
661:
662: // ///////////////////////////////////////////////////////////////////////////////////////////////
663: // command listener
664: static class CommandListener implements ActionListener {
665: private java.lang.ref.WeakReference m_dlgref;
666:
667: public CommandListener(JETADialog dlg) {
668: m_dlgref = new java.lang.ref.WeakReference(dlg);
669: }
670:
671: public void actionPerformed(ActionEvent e) {
672: String actionCommand = e.getActionCommand();
673: JETADialog dlg = (JETADialog) m_dlgref.get();
674: if (dlg != null)
675: dlg.actionPerformed(actionCommand, e);
676: }
677: }
678:
679: /**
680: * This class maintains an association between a rule and the parameters
681: * used to pass to that rule
682: */
683: static class ValidatorRule {
684: /** the rule that performns validation on the dialog */
685: private JETARule m_rule;
686:
687: /** a list of parameters to pass to the rule */
688: private Object[] m_parameters;
689:
690: /**
691: * ctor
692: */
693: public ValidatorRule(JETARule rule, Object[] params) {
694: m_rule = rule;
695: m_parameters = params;
696: }
697:
698: /**
699: * @return the parameters to pass to the rule
700: */
701: public Object[] getParameters() {
702: return m_parameters;
703: }
704:
705: /**
706: * @return the rule responsible for validating the dialog
707: */
708: public JETARule getRule() {
709: return m_rule;
710: }
711: }
712: }
|