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         *     &lt;exec&gt;com.sun.foo.FubarHandler#handleBar&lt;/exec&gt;
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    }