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.Component;
033: import java.awt.event.ActionEvent;
034: import java.awt.event.ActionListener;
035: import java.awt.event.FocusEvent;
036: import java.awt.event.FocusListener;
037: import java.lang.reflect.Method;
038: import java.util.Collection;
039: import java.util.EventObject;
040: import java.util.HashMap;
041: import java.util.Iterator;
042:
043: import javax.swing.AbstractButton;
044: import javax.swing.JComboBox;
045: import javax.swing.JList;
046: import javax.swing.JTextField;
047: import javax.swing.event.ChangeEvent;
048: import javax.swing.event.ChangeListener;
049: import javax.swing.event.ListSelectionEvent;
050: import javax.swing.event.ListSelectionListener;
051:
052: /**
053: * This is the base class for all controllers in an application. We follow the
054: * model-view-controller pattern extensively in the UI. All controllers should
055: * derive from this class. A controller is resposible for all event handlers for
056: * a JETAPanel (JETAPanels handle only layout and view logic). This controller
057: * class also supports disabling listeners. This is useful when updating a view
058: * with components that fire events when changed programmatically such as
059: * JComboBoxes. See {@link #enableEvents}. Usage:
060: *
061: * <pre>
062: * class MyController extends JETAController {
063: * public MyController(JETAContainer view) {
064: * super(view);
065: * // assign a button handler. If the view has a menu and toolbar button
066: * // for the same command, this assignAction method will install handlers
067: * // on both.
068: * assignAction(MyViewNames.ID_LOGIN_BUTTON, new LoginAction());
069: * // assign other listeners
070: * }
071: *
072: * public class LoginAction implements ActionListener {
073: * public void actionPerformed(ActionEvent evt) {
074: * MyFrame frame = (MyFrame) getView();
075: * //...
076: * }
077: * }
078: * }
079: * </pre>
080: *
081: *
082: * @author Jeff Tassin
083: */
084: public abstract class JETAController {
085: /**
086: * The view we are handling events for
087: */
088: private JETAContainer m_view;
089:
090: /**
091: * The single ActionListener for those components that take action
092: * listeners.
093: */
094:
095: private CommandListener m_cmdListener = new CommandListener();
096:
097: /**
098: * A map of CommandNames to ActionListeners m_commands<String,ActionListener>
099: *
100: * @deprecated
101: */
102: private ListenerManager m_action_listeners = new ListenerManager();
103:
104: /**
105: * The single ListListener for JList components
106: */
107: private ListListener m_llistener = new ListListener();
108: private ListenerManager m_list_listeners;
109:
110: /**
111: * ChangeListener Support
112: */
113: private ComponentChangeListener m_clistener = new ComponentChangeListener();
114: private ListenerManager m_change_listeners;
115:
116: private TextFocusListener m_text_focus_listener = new TextFocusListener();
117: private HashMap m_txt_values = new HashMap();
118:
119: /**
120: * Flag that indicates if listener/action events should be propagated to
121: * registered listeners
122: */
123: private boolean m_events_enabled = true;
124:
125: /**
126: * ctor
127: *
128: * @param view
129: * the view whose events we are handling.
130: */
131: public JETAController(JETAContainer view) {
132: m_view = view;
133: }
134:
135: /**
136: * Forwarded from the view when an action occurs. This method Lookups up the
137: * name of any ActionListener in the m_commands hash map that corresponds to
138: * the actionName. It then invokes the
139: *
140: * @see java.awt.ActionListener#actionPerformed method if a component is
141: * found.
142: * @param actionName
143: * the name of the action associated with the event
144: * @param evt
145: * the action event
146: * @return true if the controller handled the action
147: */
148: public boolean actionPerformed(String actionName, ActionEvent evt) {
149: if (isEventsEnabled()) {
150: boolean bresult = false;
151: ActionListener listener = (ActionListener) m_action_listeners
152: .getListener(actionName);
153:
154: if (listener != null) {
155: listener.actionPerformed(evt);
156: bresult = true;
157: }
158:
159: JETAContainer view = getView();
160: if (view != null) {
161: UIDirector uidirector = view.getUIDirector();
162: if (uidirector != null)
163: uidirector.updateComponents(evt);
164: }
165:
166: return bresult;
167: } else {
168: return false;
169: }
170: }
171:
172: /**
173: * Registers and ActionListener with this controller. This controller adds
174: * itself as a listener to all components with the given name in the
175: * associated view.
176: */
177: public void assignAction(String compName, ActionListener action) {
178: assignAction(getView(), compName, action);
179: }
180:
181: /**
182: * The preferred way to register actions. This elimiates the need to
183: * register the button with the view's actionListener.
184: */
185: private void assignAction(JETAContainer view, String commandId,
186: ActionListener action) {
187: Collection comps = view.getComponentsByName(commandId);
188: if (comps.size() == 0) {
189: System.out
190: .println("JETAController.assignAction failed for: "
191: + commandId);
192: }
193: Iterator iter = comps.iterator();
194: while (iter.hasNext()) {
195: Component comp = (Component) iter.next();
196: if (comp != null)
197: assignComponentAction(comp, commandId, action);
198: }
199: m_action_listeners.assignListener(commandId, action);
200: }
201:
202: /**
203: * Registers this controller as an action listener with all components that
204: * have the given name found in the associated view.
205: */
206: protected void assignComponentAction(Component comp,
207: String commandId, ActionListener action) {
208: try {
209: if (comp instanceof AbstractButton) {
210: AbstractButton btn = (AbstractButton) comp;
211: /**
212: * we need to remove the listener in case this is being called
213: * for a derived view/controller. and we don't wan to
214: * re-register listeners
215: */
216: btn.removeActionListener(m_cmdListener);
217: btn.addActionListener(m_cmdListener);
218: btn.setActionCommand(commandId);
219: } else if (comp instanceof JTextField) {
220: JTextField txtfield = (JTextField) comp;
221: txtfield.removeActionListener(m_cmdListener);
222: txtfield.addActionListener(m_cmdListener);
223: txtfield.setActionCommand(commandId);
224: /**
225: * here we add a focus listener that gets focusGained/lost
226: * events. When the focus is lost, we check the field for
227: * changes
228: */
229: txtfield.addFocusListener(m_text_focus_listener);
230: } else if (comp instanceof JComboBox) {
231: JComboBox box = (JComboBox) comp;
232: box.removeActionListener(m_cmdListener);
233: box.addActionListener(m_cmdListener);
234: box.setActionCommand(commandId);
235: } else {
236:
237: Class[] params = new Class[] { ActionListener.class };
238: Object[] values = new Object[] { m_cmdListener };
239:
240: try {
241: Method m = comp.getClass().getMethod(
242: "removeActionListener", params);
243: m.invoke(comp, values);
244: } catch (Exception e) {
245:
246: }
247: Method m = comp.getClass().getMethod(
248: "addActionListener", params);
249: m.invoke(comp, values);
250: /*
251: * params = new Class[] { String.class }; values = new Object[] {
252: * commandId }; m = comp.getClass().getMethod(
253: * "setActionCommand", params ); m.invoke( comp, values );
254: */
255: }
256: } catch (Exception e) {
257: e.printStackTrace();
258: }
259: }
260:
261: /**
262: * Assigns a list listener to the JList for the given name
263: */
264: public void assignListener(String compName,
265: ListSelectionListener listener) {
266: assignListener(getView(), compName, listener);
267: }
268:
269: /**
270: * Assigns a list listener to the JList for the given name
271: */
272: private void assignListener(JETAContainer view, String compName,
273: ListSelectionListener listener) {
274: Component comp = view.getComponentByName(compName);
275: if (comp instanceof JList) {
276: JList list = (JList) comp;
277: if (list != null) {
278: if (m_list_listeners == null)
279: m_list_listeners = new ListenerManager();
280:
281: list.addListSelectionListener(m_llistener);
282: m_list_listeners.assignListener(compName, listener);
283: } else {
284: assert (false);
285: }
286: }
287: }
288:
289: /**
290: * Assigns a change listener to the Component for the given name - assuming
291: * the component has method addChangeListener( ChangeListener )
292: */
293: public void assignListener(String compName, ChangeListener listener) {
294: assignListener(getView(), compName, listener);
295: }
296:
297: /**
298: * Assigns a change listener to the Component for the given name - assuming
299: * the component has method addChangeListener( ChangeListener )
300: */
301: private void assignListener(JETAContainer view, String compName,
302: ChangeListener listener) {
303: try {
304: Component comp = view.getComponentByName(compName);
305: if (comp != null) {
306:
307: if (m_change_listeners == null)
308: m_change_listeners = new ListenerManager();
309:
310: Class[] params = new Class[] { ChangeListener.class };
311: Object[] values = new Object[] { m_clistener };
312:
313: Method m = comp.getClass().getMethod(
314: "addChangeListener", params);
315: m.invoke(comp, values);
316: m_change_listeners.assignListener(compName, listener);
317: } else {
318: assert (false);
319: }
320: } catch (Exception e) {
321: e.printStackTrace();
322: }
323: }
324:
325: /**
326: * Sets the flag that indicates if listener/action events should be
327: * propagated to registered listeners
328: */
329: public void enableEvents(boolean enable) {
330: m_events_enabled = enable;
331: }
332:
333: /**
334: * Returns the main action listener that is registered with components in
335: * the associated view.
336: *
337: * @return the main action listener
338: */
339: public ActionListener getPrimaryActionListener() {
340: return m_cmdListener;
341: }
342:
343: /**
344: * Returns the first action listener associated with the given name. If the
345: * listener is not found, null is returned
346: *
347: * @return the action listener associated with the given component.
348: */
349: public ActionListener getAction(String actionName) {
350: return (ActionListener) m_action_listeners
351: .getListener(actionName);
352: }
353:
354: /**
355: * Returns the view that is associated with this controller.
356: */
357: public JETAContainer getView() {
358: return m_view;
359: }
360:
361: /**
362: * Invokes the action assigned to the given name
363: */
364: public boolean invokeAction(String actionName) {
365: return actionPerformed(actionName, new ActionEvent(this , 0,
366: actionName));
367: }
368:
369: /**
370: * @return the flag that indicates if listener/action events should be
371: * propagated to registered listeners
372: */
373: public boolean isEventsEnabled() {
374: return m_events_enabled;
375: }
376:
377: /**
378: * Forwarded from the view when a change event occurs.
379: */
380: public void stateChanged(ChangeEvent e) {
381: if (isEventsEnabled()) {
382: if (m_change_listeners == null)
383: return;
384:
385: Component comp = (Component) e.getSource();
386:
387: ChangeListener listener = (ChangeListener) m_change_listeners
388: .getListener(comp.getName());
389: if (listener != null) {
390: listener.stateChanged(e);
391: }
392:
393: JETAContainer view = getView();
394: if (view != null) {
395: UIDirector uidirector = view.getUIDirector();
396: if (uidirector != null)
397: uidirector.updateComponents(e);
398: }
399: }
400: }
401:
402: /**
403: * Forwards the call to the UIDirector if it is not null
404: */
405: public void updateComponents(Object src) {
406: /**
407: * This is a global update call that is not specific to any event, so we
408: * create an event.
409: */
410: if (src == null)
411: src = this ;
412:
413: EventObject evt = null;
414: if (!(src instanceof EventObject))
415: evt = new EventObject(src);
416:
417: JETAContainer view = getView();
418: if (view != null) {
419: UIDirector uidirector = view.getUIDirector();
420: if (uidirector != null)
421: uidirector.updateComponents(evt);
422: }
423: }
424:
425: /**
426: * Override if you want to provide a validation in your controller.
427: *
428: * @return an error message if the validation failed. Return null if the
429: * validation succeeded
430: * @deprecated overrideValidateInputs instead
431: */
432: private final String validate() {
433: return null;
434: }
435:
436: /**
437: * Override if you want to provide a validation in your controller.
438: *
439: * @return an error message if the validation failed. Return null if the
440: * validation succeeded
441: */
442: private final String validateInputs() {
443: return validate();
444: }
445:
446: /**
447: * Forwarded from the view when a list selection event occurs. This method
448: *
449: * @param e
450: * the list selection event
451: */
452: public void valueChanged(ListSelectionEvent e) {
453: if (isEventsEnabled()) {
454: if (m_list_listeners == null)
455: return;
456:
457: JList list = (JList) e.getSource();
458:
459: ListSelectionListener listener = (ListSelectionListener) m_list_listeners
460: .getListener(list.getName());
461: if (listener != null) {
462: listener.valueChanged(e);
463: }
464:
465: JETAContainer view = getView();
466: if (view != null) {
467: UIDirector uidirector = view.getUIDirector();
468: if (uidirector != null)
469: uidirector.updateComponents(e);
470: }
471: }
472: }
473:
474: /**
475: * Override if you want to provide a custom validation in your controller.
476: * This is different from the standard validate because the controller must
477: * provide any error messages to the user.
478: *
479: * @return an false message if the validation failed. This will cause the
480: * dialog to remain on the screen
481: */
482: private final boolean validateCustom() {
483: return true;
484: }
485:
486: // ///////////////////////////////////////////////////////////////////////////////////////////////
487: // command listener
488: class CommandListener implements ActionListener {
489: public void actionPerformed(ActionEvent e) {
490: Object src = e.getSource();
491: String actionCommand = e.getActionCommand();
492: String actionName = ((Component) src).getName();
493:
494: if (src instanceof JTextField) {
495: JTextField comp = (JTextField) src;
496: m_txt_values.put(comp, comp.getText());
497: }
498:
499: if (!actionName.equals(actionCommand)) {
500: System.out
501: .println("JETAController actionCommand != componentName. actionCommand: "
502: + actionCommand
503: + " componentName: "
504: + actionName);
505: }
506: JETAController.this .actionPerformed(actionName, e);
507: }
508: }
509:
510: /**
511: * Listener for JList components
512: */
513: class ListListener implements ListSelectionListener {
514: public void valueChanged(ListSelectionEvent e) {
515: JETAController.this .valueChanged(e);
516: }
517: }
518:
519: /**
520: * Listener for those components that support change events
521: */
522: class ComponentChangeListener implements ChangeListener {
523: public void stateChanged(ChangeEvent e) {
524: JETAController.this .stateChanged(e);
525: }
526: }
527:
528: /**
529: * Focus listener for text fields
530: */
531: public class TextFocusListener implements FocusListener {
532:
533: public void focusGained(FocusEvent evt) {
534: JTextField comp = (JTextField) evt.getSource();
535: m_txt_values.put(comp, comp.getText());
536: }
537:
538: public void focusLost(FocusEvent evt) {
539: JTextField comp = (JTextField) evt.getSource();
540: String old_value = (String) m_txt_values.get(comp);
541: if (!comp.getText().equals(old_value)) {
542: JETAController.this .actionPerformed(comp.getName(),
543: new ActionEvent(comp,
544: ActionEvent.ACTION_PERFORMED, comp
545: .getName()));
546: }
547: }
548: }
549:
550: public static class ListenerManager {
551: private HashMap m_listeners = new HashMap();
552:
553: public Object getListener(String commandId) {
554: return m_listeners.get(commandId);
555: }
556:
557: /**
558: * Adds a listener to the list of listeners for a given commandid
559: */
560: public void assignListener(String commandId, Object listener) {
561: m_listeners.put(commandId, listener);
562: }
563: }
564:
565: }
|