001    /*
002     * $Id: ActionManager.java,v 1.4 2005/10/13 08:59:55 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    package org.jdesktop.swingx.action;
022    
023    import java.io.PrintStream;
024    import java.util.HashMap;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import javax.swing.AbstractAction;
029    import javax.swing.Action;
030    
031    /**
032     * The ActionManager manages sets of <code>javax.swing.Action</code>s for an
033     * application. There are convenience methods for getting and setting the state
034     * of the action.
035     * All of these elements have a unique id tag which is used by the ActionManager
036     * to reference the action. This id maps to the <code>Action.ACTION_COMMAND_KEY</code>
037     * on the Action.
038     * <p>
039     * The ActionManager may be used to conveniently register callback methods
040     * on BoundActions.
041     * <p>
042     * A typical use case of the ActionManager is:
043     * <p>
044     *  <pre>
045     *   ActionManager manager = ActionManager.getInstance();
046     *
047     *   // load Actions
048     *   manager.addAction(action);
049     *
050     *   // Change the state of the action:
051     *   manager.setEnabled("new-action", newState);
052     * </pre>
053     *
054     * The ActionManager also supports Actions that can have a selected state
055     * associated with them. These Actions are typically represented by a
056     * JCheckBox or similar widget. For such actions the registered method is
057     * invoked with an additional parameter indicating the selected state of
058     * the widget. For example, for the callback handler:
059     *<p>
060     * <pre>
061     *  public class Handler {
062     *      public void stateChanged(boolean newState);
063     *   }
064     * </pre>
065     * The registration method would look similar:
066     * <pre>
067     *  manager.registerCallback("select-action", new Handler(), "stateChanged");
068     * </pre>
069     *<p>
070     * The stateChanged method would be invoked as the selected state of
071     * the widget changed. Additionally if you need to change the selected
072     * state of the Action use the ActionManager method <code>setSelected</code>.
073     * <p>
074     * The <code>ActionContainerFactory</code> uses the managed Actions in the
075     * ActionManager to create
076     * user interface components. For example, to create a JMenu based on an
077     * action-list id:
078     * <pre>
079     * JMenu file = manager.getFactory().createMenu(list);
080     * </pre>
081     *
082     * @see ActionContainerFactory
083     * @see TargetableAction
084     * @see BoundAction
085     * @author Mark Davidson
086     */
087    public class ActionManager {
088    
089        // Internal data structures which manage the actions.
090    
091        // key: value of ID_ATTR, value instanceof AbstractAction
092        private Map actionMap;
093    
094        // Container factory instance for this ActionManager
095        private ActionContainerFactory factory;
096    
097        /**
098         * Shared instance of the singleton ActionManager.
099         */
100        private static ActionManager INSTANCE;
101    
102        // To enable debugging:
103        //   Pass -Ddebug=true to the vm on start up.
104        // or
105        //   set System.setProperty("debug", "true"); before constructing this Object
106    
107        private static boolean DEBUG = false;
108    
109        /**
110         * Creates the action manager. Use this constuctor if the application should
111         * support many ActionManagers. Otherwise, using the getInstance method will
112         * return a singleton.
113         */
114        public ActionManager() {
115        }
116    
117        /**
118         * Return the Action Container Factory associated with this ActionManager
119         * instance. Will always return a factory instance.
120         */
121        public ActionContainerFactory getFactory() {
122            if (factory == null) {
123                factory = new ActionContainerFactory(this);
124            }
125            return factory;
126        }
127    
128        /**
129         * This method should be used to associate a subclassed ActionContainerFactory
130         * with this ActionManager.
131         */
132        public void setFactory(ActionContainerFactory factory) {
133            this.factory = factory;
134        }
135    
136        /**
137         * Return the instance of the ActionManger. If this has not been explicity
138         * set then it will be created.
139         *
140         * @return the ActionManager instance.
141         * @see #setInstance
142         */
143        public static ActionManager getInstance() {
144            if (INSTANCE == null) {
145                INSTANCE = new ActionManager();
146            }
147            return INSTANCE;
148        }
149    
150        /**
151         * Sets the ActionManager instance.
152         */
153        public static void setInstance(ActionManager manager) {
154            INSTANCE = manager;
155        }
156    
157        /**
158         * Returns the ids for all the managed actions.
159         * <p>
160         * An action id is a unique idenitfier which can
161         * be used to retrieve the corrspondng Action from the ActionManager.
162         * This identifier can also
163         * be used to set the properties of the action through the action
164         * manager like setting the state of the enabled or selected flags.
165         *
166         * @return a set which represents all the action ids
167         */
168        public Set getActionIDs() {
169            if (actionMap == null) {
170                return null;
171            }
172            return actionMap.keySet();
173        }
174    
175        public Action addAction(Action action) {
176            return addAction(action.getValue(Action.ACTION_COMMAND_KEY), action);
177        }
178    
179        /**
180         * Adds an action to the ActionManager
181         * @param id value of the action id - which is value of the ACTION_COMMAND_KEY
182         * @param action Action to be managed
183         * @return the action that was added
184         */
185        public Action addAction(Object id, Action action)  {
186            if (actionMap == null) {
187                actionMap = new HashMap();
188            }
189            actionMap.put(id, action);
190    
191            return action;
192        }
193    
194        /**
195         * Retrieves the action corresponding to an action id.
196         *
197         * @param id value of the action id
198         * @return an Action or null if id
199         */
200        public Action getAction(Object id)  {
201            if (actionMap != null) {
202                return (Action)actionMap.get(id);
203            }
204            return null;
205        }
206    
207        /**
208         * Convenience method for returning the TargetableAction
209         *
210         * @param id value of the action id
211         * @return the TargetableAction referenced by the named id or null
212         */
213        public TargetableAction getTargetableAction(Object id) {
214            Action a = getAction(id);
215            if (a instanceof TargetableAction) {
216                return (TargetableAction)a;
217            }
218            return null;
219        }
220    
221        /**
222         * Convenience method for returning the BoundAction
223         *
224         * @param id value of the action id
225         * @return the TargetableAction referenced by the named id or null
226         */
227        public BoundAction getBoundAction(Object id) {
228            Action a = getAction(id);
229            if (a instanceof BoundAction) {
230                return (BoundAction)a;
231            }
232            return null;
233        }
234    
235        /**
236         * Convenience method for returning the ServerAction
237         *
238         * @param id value of the action id
239         * @return the TargetableAction referenced by the named id or null
240         */
241        public ServerAction getServerAction(Object id) {
242            Action a = getAction(id);
243            if (a instanceof ServerAction) {
244                return (ServerAction)a;
245            }
246            return null;
247        }
248    
249        /**
250         * Convenience method for returning the CompositeAction
251         *
252         * @param id value of the action id
253         * @return the TargetableAction referenced by the named id or null
254         */
255        public CompositeAction getCompositeAction(Object id) {
256            Action a = getAction(id);
257            if (a instanceof CompositeAction) {
258                return (CompositeAction)a;
259            }
260            return null;
261        }
262    
263        /**
264         * Convenience method for returning the StateChangeAction
265         *
266         * @param id value of the action id
267         * @return the StateChangeAction referenced by the named id or null
268         */
269        private AbstractActionExt getStateChangeAction(Object id) {
270            Action a = getAction(id);
271            if (a != null && a instanceof AbstractActionExt) {
272                AbstractActionExt aa = (AbstractActionExt)a;
273                if (aa.isStateAction()) {
274                    return aa;
275                }
276            }
277            return null;
278        }
279    
280        /**
281         * Enables or disables the state of the Action corresponding to the
282         * action id. This method should be used
283         * by application developers to ensure that all components created from an
284         * action remain in synch with respect to their enabled state.
285         *
286         * @param id value of the action id
287         * @param enabled true if the action is to be enabled; otherwise false
288         */
289        public void setEnabled(Object id, boolean enabled) {
290            Action action = getAction(id);
291            if (action != null) {
292                action.setEnabled(enabled);
293            }
294        }
295    
296    
297        /**
298         * Returns the enabled state of the <code>Action</code>. When enabled,
299         * any component associated with this object is active and
300         * able to fire this object's <code>actionPerformed</code> method.
301         *
302         * @param id value of the action id
303         * @return true if this <code>Action</code> is enabled; false if the
304         *         action doesn't exist or disabled.
305         */
306        public boolean isEnabled(Object id) {
307            Action action = getAction(id);
308            if (action != null) {
309                return action.isEnabled();
310            }
311            return false;
312        }
313    
314        /**
315         * Sets the selected state of a toggle action. If the id doesn't
316         * correspond to a toggle action then it will fail silently.
317         *
318         * @param id the value of the action id
319         * @param selected true if the action is to be selected; otherwise false.
320         */
321        public void setSelected(Object id, boolean selected) {
322            AbstractActionExt action = getStateChangeAction(id);
323            if (action != null) {
324                action.setSelected(selected);
325            }
326        }
327    
328        /**
329         * Gets the selected state of a toggle action. If the id doesn't
330         * correspond to a toggle action then it will fail silently.
331         *
332         * @param id the value of the action id
333         * @return true if the action is selected; false if the action
334         *         doesn't exist or is disabled.
335         */
336        public boolean isSelected(Object id) {
337            AbstractActionExt action = getStateChangeAction(id);
338            if (action != null) {
339                return action.isSelected();
340            }
341            return false;
342        }
343    
344        /**
345         * A diagnostic which prints the Attributes of an action
346         * on the printStream
347         */
348        static void printAction(PrintStream stream, Action action) {
349            stream.println("Attributes for " + action.getValue(Action.ACTION_COMMAND_KEY));
350    
351            if (action instanceof AbstractAction) {
352                Object[] keys = ((AbstractAction)action).getKeys();
353    
354                for (int i = 0; i < keys.length; i++) {
355                    stream.println("\tkey: " + keys[i] + "\tvalue: " +
356                                   action.getValue((String)keys[i]));
357                }
358            }
359        }
360    
361        /**
362         * Convenience method to register a callback method on a <code>BoundAction</code>
363         *
364         * @see BoundAction#registerCallback
365         * @param id value of the action id - which is the value of the ACTION_COMMAND_KEY
366         * @param handler the object which will be perform the action
367         * @param method the name of the method on the handler which will be called.
368         */
369        public void registerCallback(Object id, Object handler, String method) {
370            BoundAction action = getBoundAction(id);
371            if (action != null) {
372                action.registerCallback(handler, method);
373            }
374        }
375    
376        //
377        // Convenience methods for determining the type of action.
378        //
379    
380        /**
381         * Determines if the Action corresponding to the action id is a state changed
382         * action (toggle, group type action).
383         *
384         * @param id value of the action id
385         * @return true if the action id represents a multi state action; false otherwise
386         */
387        public boolean isStateAction(Object id) {
388            Action action = getAction(id);
389            if (action != null && action instanceof AbstractActionExt) {
390                return ((AbstractActionExt)action).isStateAction();
391            }
392            return false;
393        }
394    
395        /**
396         * Test to determine if the action is a <code>TargetableAction</code>
397         */
398        public boolean isTargetableAction(Object id) {
399            return (getTargetableAction(id) != null);
400        }
401    
402        /**
403         * Test to determine if the action is a <code>BoundAction</code>
404         */
405        public boolean isBoundAction(Object id) {
406            return (getBoundAction(id) != null);
407        }
408    
409        /**
410         * Test to determine if the action is a <code>BoundAction</code>
411         */
412        public boolean isCompositeAction(Object id) {
413            return (getCompositeAction(id) != null);
414        }
415    
416        /**
417         * Test to determine if the action is a <code>ServerAction</code>
418         */
419        public boolean isServerAction(Object id) {
420            return (getServerAction(id) != null);
421        }
422    }