001 /*
002 * $Id: BoundAction.java,v 1.4 2005/10/26 11:44:31 kleopatra Exp $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021
022 package org.jdesktop.swingx.action;
023
024 import java.awt.event.ActionEvent;
025 import java.awt.event.ActionListener;
026 import java.awt.event.ItemEvent;
027 import java.awt.event.ItemListener;
028 import java.beans.EventHandler;
029 import java.beans.Statement;
030 import java.util.EventListener;
031 import java.util.logging.Level;
032 import java.util.logging.Logger;
033
034 import javax.swing.Icon;
035 import javax.swing.event.EventListenerList;
036
037 /**
038 * A class that represents the many type of actions that this framework supports.
039 * <p>
040 * The command invocation of this action may be delegated to another action or item state
041 * listener. If there isn't an explicit binding then the command is forwarded to
042 * the TargetManager.
043 *
044 * @author Mark Davidson
045 */
046 public class BoundAction extends AbstractActionExt {
047 private static final Logger LOG = Logger.getLogger(BoundAction.class
048 .getName());
049 // Holds the listeners
050 private EventListenerList listeners;
051
052 public BoundAction() {
053 this("BoundAction");
054 }
055
056 public BoundAction(String name) {
057 super(name);
058 }
059
060 /**
061 * @param name display name of the action
062 * @param command the value of the action command key
063 */
064 public BoundAction(String name, String command) {
065 super(name, command);
066 }
067
068 public BoundAction(String name, Icon icon) {
069 super(name, icon);
070 }
071
072 /**
073 * @param name display name of the action
074 * @param command the value of the action command key
075 * @param icon icon to display
076 */
077 public BoundAction(String name, String command, Icon icon) {
078 super(name, command, icon);
079 }
080
081 /**
082 * The callback string will be called to register the action callback.
083 * Note the toggle property must be set if this is a state action before
084 * this method is called.
085 * For example,
086 * <pre>
087 * <exec>com.sun.foo.FubarHandler#handleBar</exec>
088 * </pre>
089 * will register
090 * <pre>
091 * registerCallback(com.sun.foo.FubarHandler(), "handleBar");
092 * </pre>
093 */
094 public void setCallback(String callback) {
095 String[] elems = callback.split("#", 2);
096 if (elems.length == 2) {
097 try {
098 Class clz = Class.forName(elems[0]);
099
100 // May throw a security exception in an Applet
101 // context.
102 Object obj = clz.newInstance();
103
104 registerCallback(obj, elems[1]);
105 } catch (Exception ex) {
106 LOG.fine("ERROR: setCallback(" + callback
107 + ") - " + ex.getMessage());
108 }
109 }
110 }
111
112 /**
113 * Registers a callback method when the Action corresponding to
114 * the action id is invoked. When a Component that was constructed from the
115 * Action identified by the action id invokes actionPerformed then the method
116 * named will be invoked on the handler Object.
117 * <p>
118 * If the Action represented by the action id is a StateChangeAction, then
119 * the method passed should take an int as an argument. The value of
120 * getStateChange() on the ItemEvent object will be passed as the parameter.
121 *
122 * @param handler the object which will be perform the action
123 * @param method the name of the method on the handler which will be called.
124 */
125 public void registerCallback(Object handler, String method) {
126 if (isStateAction()) {
127 // Create a handler for toogle type actions.
128 addItemListener(new BooleanInvocationHandler(handler, method));
129 } else {
130 // Create a new ActionListener using the dynamic proxy api.
131 addActionListener((ActionListener)EventHandler.create(ActionListener.class,
132 handler, method));
133 }
134 }
135
136 /**
137 * The callback for the toggle/state changed action that invokes a method
138 * with a boolean argument on a target.
139 *
140 * TODO: should reimplement this class as something that can be persistable.
141 */
142 private class BooleanInvocationHandler implements ItemListener {
143
144 private Statement falseStatement;
145 private Statement trueStatement;
146
147 public BooleanInvocationHandler(Object target, String methodName) {
148 // Create the true and false statements.
149 falseStatement = new Statement(target, methodName,
150 new Object[] { Boolean.FALSE });
151
152 trueStatement = new Statement(target, methodName,
153 new Object[] { Boolean.TRUE });
154 }
155
156 public void itemStateChanged(ItemEvent evt) {
157 Statement statement = (evt.getStateChange() == ItemEvent.DESELECTED) ? falseStatement
158 : trueStatement;
159
160 try {
161 statement.execute();
162 } catch (Exception ex) {
163 LOG.log(Level.FINE,
164 "Couldn't execute boolean method via Statement "
165 + statement, ex);
166 }
167 }
168 }
169
170 // Listener registration...
171
172 private void addListener(Class clz, EventListener listener) {
173 if (listeners == null) {
174 listeners = new EventListenerList();
175 }
176 listeners.add(clz, listener);
177 }
178
179 private void removeListener(Class clz, EventListener listener) {
180 if (listeners != null) {
181 listeners.remove(clz, listener);
182 }
183 }
184
185 private EventListener[] getListeners(Class clz) {
186 if (listeners == null) {
187 return null;
188 }
189 return listeners.getListeners(clz);
190 }
191
192 /**
193 * Add an action listener which will be invoked when this action is invoked.
194 */
195 public void addActionListener(ActionListener listener) {
196 addListener(ActionListener.class, listener);
197 }
198
199 public void removeActionListener(ActionListener listener) {
200 removeListener(ActionListener.class, listener);
201 }
202
203 public ActionListener[] getActionListeners() {
204 return (ActionListener[])getListeners(ActionListener.class);
205 }
206
207 /**
208 * Add an item listener which will be invoked for toggle actions.
209 */
210 public void addItemListener(ItemListener listener) {
211 addListener(ItemListener.class, listener);
212 }
213
214 public void removeItemListener(ItemListener listener) {
215 removeListener(ItemListener.class, listener);
216 }
217
218 public ItemListener[] getItemListeners() {
219 return (ItemListener[])getListeners(ItemListener.class);
220 }
221
222 // Callbacks...
223
224 /**
225 * Callback for command actions.
226 */
227 public void actionPerformed(ActionEvent evt) {
228 ActionListener[] alist = getActionListeners();
229 if (alist != null) {
230 for (int i = 0 ; i < alist.length; i++) {
231 alist[i].actionPerformed(evt);
232 }
233 }
234 }
235
236 /**
237 * Callback for toggle actions.
238 */
239 public void itemStateChanged(ItemEvent evt) {
240 // Update all objects that share this item
241 boolean newValue;
242 boolean oldValue = isSelected();
243
244 if (evt.getStateChange() == ItemEvent.SELECTED) {
245 newValue = true;
246 } else {
247 newValue = false;
248 }
249
250 if (oldValue != newValue) {
251 setSelected(newValue);
252
253 // Forward the event to the delgate for handling.
254 ItemListener[] ilist = getItemListeners();
255 if (ilist != null) {
256 for (int i = 0; i < ilist.length; i++) {
257 ilist[i].itemStateChanged(evt);
258 }
259 }
260 }
261 }
262
263 }