001 /*
002 * $Id: BoundAction.java 3132 2008-12-05 14:34:58Z kschaefe $
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 javax.swing.*;
025 import javax.swing.event.EventListenerList;
026 import java.awt.event.ActionEvent;
027 import java.awt.event.ActionListener;
028 import java.awt.event.ItemEvent;
029 import java.awt.event.ItemListener;
030 import java.beans.EventHandler;
031 import java.beans.Statement;
032 import java.util.EventListener;
033 import java.util.logging.Level;
034 import java.util.logging.Logger;
035
036 /**
037 * A class that represents the many type of actions that this framework supports.
038 * <p>
039 * The command invocation of this action may be delegated to another action or item state
040 * listener. If there isn't an explicit binding then the command is forwarded to
041 * the TargetManager.
042 *
043 * @author Mark Davidson
044 */
045 public class BoundAction extends AbstractActionExt {
046 private static final Logger LOG = Logger.getLogger(BoundAction.class
047 .getName());
048 // Holds the listeners
049 private EventListenerList listeners;
050
051 public BoundAction() {
052 this("BoundAction");
053 }
054
055 public BoundAction(String name) {
056 super(name);
057 }
058
059 /**
060 * @param name display name of the action
061 * @param command the value of the action command key
062 */
063 public BoundAction(String name, String command) {
064 super(name, command);
065 }
066
067 public BoundAction(String name, Icon icon) {
068 super(name, icon);
069 }
070
071 /**
072 * @param name display name of the action
073 * @param command the value of the action command key
074 * @param icon icon to display
075 */
076 public BoundAction(String name, String command, Icon icon) {
077 super(name, command, icon);
078 }
079
080 /**
081 * The callback string will be called to register the action callback.
082 * Note the toggle property must be set if this is a state action before
083 * this method is called.
084 * For example,
085 * <pre>
086 * <exec>com.sun.foo.FubarHandler#handleBar</exec>
087 * </pre>
088 * will register
089 * <pre>
090 * registerCallback(com.sun.foo.FubarHandler(), "handleBar");
091 * </pre>
092 */
093 public void setCallback(String callback) {
094 String[] elems = callback.split("#", 2);
095 if (elems.length == 2) {
096 try {
097 Class<?> clz = Class.forName(elems[0]);
098
099 // May throw a security exception in an Applet
100 // context.
101 Object obj = clz.newInstance();
102
103 registerCallback(obj, elems[1]);
104 } catch (Exception ex) {
105 LOG.fine("ERROR: setCallback(" + callback
106 + ") - " + ex.getMessage());
107 }
108 }
109 }
110
111 /**
112 * Registers a callback method when the Action corresponding to
113 * the action id is invoked. When a Component that was constructed from the
114 * Action identified by the action id invokes actionPerformed then the method
115 * named will be invoked on the handler Object.
116 * <p>
117 * If the Action represented by the action id is a StateChangeAction, then
118 * the method passed should take an int as an argument. The value of
119 * getStateChange() on the ItemEvent object will be passed as the parameter.
120 *
121 * @param handler the object which will be perform the action
122 * @param method the name of the method on the handler which will be called.
123 */
124 public void registerCallback(Object handler, String method) {
125 if (isStateAction()) {
126 // Create a handler for toogle type actions.
127 addItemListener(new BooleanInvocationHandler(handler, method));
128 } else {
129 // Create a new ActionListener using the dynamic proxy api.
130 addActionListener((ActionListener)EventHandler.create(ActionListener.class,
131 handler, method));
132 }
133 }
134
135 /**
136 * The callback for the toggle/state changed action that invokes a method
137 * with a boolean argument on a target.
138 *
139 * TODO: should reimplement this class as something that can be persistable.
140 */
141 private class BooleanInvocationHandler implements ItemListener {
142
143 private Statement falseStatement;
144 private Statement trueStatement;
145
146 public BooleanInvocationHandler(Object target, String methodName) {
147 // Create the true and false statements.
148 falseStatement = new Statement(target, methodName,
149 new Object[] { Boolean.FALSE });
150
151 trueStatement = new Statement(target, methodName,
152 new Object[] { Boolean.TRUE });
153 }
154
155 public void itemStateChanged(ItemEvent evt) {
156 Statement statement = (evt.getStateChange() == ItemEvent.DESELECTED) ? falseStatement
157 : trueStatement;
158
159 try {
160 statement.execute();
161 } catch (Exception ex) {
162 LOG.log(Level.FINE,
163 "Couldn't execute boolean method via Statement "
164 + statement, ex);
165 }
166 }
167 }
168
169 // Listener registration...
170
171 private <T extends EventListener> void addListener(Class<T> clz, T listener) {
172 if (listeners == null) {
173 listeners = new EventListenerList();
174 }
175 listeners.add(clz, listener);
176 }
177
178 private <T extends EventListener> void removeListener(Class<T> clz, T listener) {
179 if (listeners != null) {
180 listeners.remove(clz, listener);
181 }
182 }
183
184 private EventListener[] getListeners(Class<? extends EventListener> clz) {
185 if (listeners == null) {
186 return null;
187 }
188 return listeners.getListeners(clz);
189 }
190
191 /**
192 * Add an action listener which will be invoked when this action is invoked.
193 */
194 public void addActionListener(ActionListener listener) {
195 addListener(ActionListener.class, listener);
196 }
197
198 public void removeActionListener(ActionListener listener) {
199 removeListener(ActionListener.class, listener);
200 }
201
202 public ActionListener[] getActionListeners() {
203 return (ActionListener[])getListeners(ActionListener.class);
204 }
205
206 /**
207 * Add an item listener which will be invoked for toggle actions.
208 */
209 public void addItemListener(ItemListener listener) {
210 addListener(ItemListener.class, listener);
211 }
212
213 public void removeItemListener(ItemListener listener) {
214 removeListener(ItemListener.class, listener);
215 }
216
217 public ItemListener[] getItemListeners() {
218 return (ItemListener[])getListeners(ItemListener.class);
219 }
220
221 // Callbacks...
222
223 /**
224 * Callback for command actions.
225 */
226 public void actionPerformed(ActionEvent evt) {
227 ActionListener[] alist = getActionListeners();
228 if (alist != null) {
229 for (int i = 0 ; i < alist.length; i++) {
230 alist[i].actionPerformed(evt);
231 }
232 }
233 }
234
235 /**
236 * Callback for toggle actions.
237 */
238 public void itemStateChanged(ItemEvent evt) {
239 // Update all objects that share this item
240 boolean newValue;
241 boolean oldValue = isSelected();
242
243 newValue = evt.getStateChange() == ItemEvent.SELECTED;
244
245 if (oldValue != newValue) {
246 setSelected(newValue);
247
248 // Forward the event to the delgate for handling.
249 ItemListener[] ilist = getItemListeners();
250 if (ilist != null) {
251 for (int i = 0; i < ilist.length; i++) {
252 ilist[i].itemStateChanged(evt);
253 }
254 }
255 }
256 }
257
258 }