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