001/*
002 * $Id: AbstractActionExt.java 4158 2012-02-03 18:29:40Z 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
022package org.jdesktop.swingx.action;
023
024import java.awt.event.ItemEvent;
025import java.awt.event.ItemListener;
026import java.beans.PropertyChangeListener;
027
028import javax.swing.AbstractAction;
029import javax.swing.Action;
030import javax.swing.Icon;
031import javax.swing.KeyStroke;
032
033/**
034 * Extends the concept of the Action to include toggle or group states.
035 * <p>
036 * SwingX 1.6.3 updates {@code AbstractActionExt} to use new features of {@link Action} that were
037 * added in {@code Java 1.6}. The selection is now managed with {@link Action#SELECTED_KEY}, which
038 * allows the action to correctly configured Swing buttons. The {@link #LARGE_ICON} has also been
039 * changed to correspond to {@link Action#LARGE_ICON_KEY}.
040 * 
041 */
042public abstract class AbstractActionExt extends AbstractAction
043    implements ItemListener {
044
045    /**
046     * The key for the large icon
047     * <p>
048     * As of SwingX 1.6.3 is now has the same value as {@link Action#LARGE_ICON_KEY}, which is new to 1.6.
049     */
050    public static final String LARGE_ICON = Action.LARGE_ICON_KEY;
051
052    /**
053     * The key for the button group
054     */
055    public static final String GROUP = "__Group__";
056
057    /**
058     * The key for the flag which indicates that this is a state type.
059     */
060    public static final String IS_STATE = "__State__";
061
062    /**
063     * Default constructor, does nothing.
064     */
065    public AbstractActionExt() {
066        this((String) null);
067    }
068    
069    /**
070     * Copy constructor copies the state.
071     */
072    public AbstractActionExt(AbstractActionExt action) {
073        Object[] keys = action.getKeys();
074        for (int i = 0; i < keys.length; i++) {
075            putValue((String)keys[i], action.getValue((String)keys[i]));
076        }
077        this.enabled = action.enabled;
078
079        // Copy change listeners.
080        PropertyChangeListener[] listeners = action.getPropertyChangeListeners();
081        for (int i = 0; i < listeners.length; i++) {
082            addPropertyChangeListener(listeners[i]);
083        }
084    }
085
086    public AbstractActionExt(String name) {
087        super(name);
088    }
089
090    public AbstractActionExt(String name, Icon icon) {
091        super(name, icon);
092    }
093
094    /**
095     * Constructs an Action with the label and command
096     *
097     * @param name name of the action usually used as a label
098     * @param command command key of the action
099     */
100    public AbstractActionExt(String name, String command) {
101        this(name);
102        setActionCommand(command);
103    }
104
105    /**
106     * @param name display name of the action
107     * @param command the value of the action command key
108     * @param icon icon to display
109     */
110    public AbstractActionExt(String name, String command, Icon icon) {
111        super(name, icon);
112        setActionCommand(command);
113    }
114    /**
115     * Returns a short description of the action.
116     *
117     * @return the short description or null
118     */
119    public String getShortDescription()  {
120        return (String)getValue(Action.SHORT_DESCRIPTION);
121    }
122
123    /**
124     * Sets the short description of the action. This will also
125     * set the long description value is it is null.
126     * <p>
127     * This is a convenience method for <code>putValue</code> with the
128     * <code>Action.SHORT_DESCRIPTION</code> key.
129     *
130     * @param desc the short description; can be <code>null</code>w
131     * @see Action#SHORT_DESCRIPTION
132     * @see Action#putValue
133     */
134    public void setShortDescription(String desc) {
135        putValue(Action.SHORT_DESCRIPTION, desc);
136        if (desc != null && getLongDescription() == null) {
137            setLongDescription(desc);
138        }
139    }
140
141    /**
142     * Returns a long description of the action.
143     *
144     * @return the long description or null
145     */
146    public String getLongDescription()  {
147        return (String)getValue(Action.LONG_DESCRIPTION);
148    }
149
150    /**
151     * Sets the long description of the action. This will also set the
152     * value of the short description if that value is null.
153     * <p>
154     * This is a convenience method for <code>putValue</code> with the
155     * <code>Action.LONG_DESCRIPTION</code> key.
156     *
157     * @param desc the long description; can be <code>null</code>
158     * @see Action#LONG_DESCRIPTION
159     * @see Action#putValue
160     */
161    public void setLongDescription(String desc) {
162        putValue(Action.LONG_DESCRIPTION, desc);
163        if (desc != null && getShortDescription() == null) {
164            setShortDescription(desc);
165        }
166    }
167
168    /**
169     * Returns a small icon which represents the action.
170     *
171     * @return the small icon or null
172     */
173    public Icon getSmallIcon() {
174        return (Icon)getValue(SMALL_ICON);
175    }
176
177    /**
178     * Sets the small icon which represents the action.
179     * <p>
180     * This is a convenience method for <code>putValue</code> with the
181     * <code>Action.SMALL_ICON</code> key.
182     *
183     * @param icon the small icon; can be <code>null</code>
184     * @see Action#SMALL_ICON
185     * @see Action#putValue
186     */
187    public void setSmallIcon(Icon icon) {
188        putValue(SMALL_ICON, icon);
189    }
190
191    /**
192     * Returns a large icon which represents the action.
193     *
194     * @return the large icon or null
195     */
196    public Icon getLargeIcon() {
197        return (Icon)getValue(LARGE_ICON);
198    }
199
200    /**
201     * Sets the large icon which represents the action.
202     * <p>
203     * This is a convenience method for <code>putValue</code> with the
204     * <code>LARGE_ICON</code> key.
205     *
206     * @param icon the large icon; can be <code>null</code>
207     * @see #LARGE_ICON
208     * @see Action#putValue
209     */
210    public void setLargeIcon(Icon icon) {
211        putValue(LARGE_ICON, icon);
212    }
213
214    /**
215     * Sets the name of the action.
216     * <p>
217     * This is a convenience method for <code>putValue</code> with the
218     * <code>Action.NAME</code> key.
219     *
220     * @param name the name of the action; can be <code>null</code>
221     * @see Action#NAME
222     * @see Action#putValue
223     */
224    public void setName(String name) {
225        putValue(Action.NAME, name);
226    }
227
228    /**
229     * Returns the name of the action.
230     *
231     * @return the name of the action or null
232     */
233    public String getName() {
234        return (String)getValue(Action.NAME);
235    }
236
237    public void setMnemonic(String mnemonic) {
238        if (mnemonic != null && mnemonic.length() > 0) {
239            putValue(Action.MNEMONIC_KEY, new Integer(mnemonic.charAt(0)));
240        }
241    }
242
243    /**
244     * Sets the mnemonic key code for the action.
245     * <p>
246     * This is a convenience method for <code>putValue</code> with the
247     * <code>Action.MNEMONIC_KEY</code> key.
248     * <p>
249     * This method does not validate the value. Please see
250     * {@link javax.swing.AbstractButton#setMnemonic(int)} for details
251     * concerning the value of the mnemonic.
252     *
253     * @param mnemonic an int key code mnemonic or 0
254     * @see javax.swing.AbstractButton#setMnemonic(int)
255     * @see Action#MNEMONIC_KEY
256     * @see Action#putValue
257     */
258    public void setMnemonic(int mnemonic) {
259        putValue(Action.MNEMONIC_KEY, new Integer(mnemonic));
260    }
261
262    /**
263     * Return the mnemonic key code for the action.
264     *
265     * @return the mnemonic or 0
266     */
267    public int getMnemonic() {
268        Integer value = (Integer)getValue(Action.MNEMONIC_KEY);
269        if (value != null) {
270            return value.intValue();
271        }
272        return '\0';
273    }
274    
275    /**
276     * Sets the action command key. The action command key
277     * is used to identify the action.
278     * <p>
279     * This is a convenience method for <code>putValue</code> with the
280     * <code>Action.ACTION_COMMAND_KEY</code> key.
281     *
282     * @param key the action command
283     * @see Action#ACTION_COMMAND_KEY
284     * @see Action#putValue
285     */
286    public void setActionCommand(String key) {
287        putValue(Action.ACTION_COMMAND_KEY, key);
288    }
289
290    /**
291     * Returns the action command.
292     * 
293     * @return the action command or null
294     */
295    public String getActionCommand() {
296        return (String) getValue(Action.ACTION_COMMAND_KEY);
297    }
298
299    /**
300     * Returns the key stroke which represents an accelerator
301     * for the action.
302     *
303     * @return the key stroke or null
304     */
305    public KeyStroke getAccelerator() {
306        return (KeyStroke)getValue(Action.ACCELERATOR_KEY);
307    }
308
309    /**
310     * Sets the key stroke which represents an accelerator
311     * for the action.
312     * <p>
313     * This is a convenience method for <code>putValue</code> with the
314     * <code>Action.ACCELERATOR_KEY</code> key.
315     *
316     * @param key the key stroke; can be <code>null</code>
317     * @see Action#ACCELERATOR_KEY
318     * @see Action#putValue
319     */
320    public void setAccelerator(KeyStroke key) {
321        putValue(Action.ACCELERATOR_KEY, key);
322    }
323
324    /**
325     * Sets the group identity of the state action. This is used to
326     * identify the action as part of a button group.
327     */
328    public void setGroup(Object group) {
329        putValue(GROUP, group);
330    }
331
332    public Object getGroup() {
333        return getValue(GROUP);
334    }
335
336    /**
337     * Will perform cleanup on the object.
338     * Should be called when finished with the Action. This should be used if
339     * a new action is constructed from the properties of an old action.
340     * The old action properties should be disposed.
341     */
342    public void dispose() {
343        PropertyChangeListener[] listeners = getPropertyChangeListeners();
344        for (int i = 0; i < listeners.length; i++) {
345            removePropertyChangeListener(listeners[i]);
346        }
347    }
348
349    // Properties etc....
350
351    /**
352     * Indicates if this action has states. If this method returns
353     * true then the this will send ItemEvents to ItemListeners
354     * when the control constructed with this action in invoked.
355     *
356     * @return true if this can handle states
357     */
358    public boolean isStateAction() {
359        Boolean state = (Boolean)getValue(IS_STATE);
360        if (state != null) {
361            return state.booleanValue();
362        }
363        return false;
364    }
365
366    /**
367     * Set the state property to true.
368     */
369    public void setStateAction() {
370        setStateAction(true);
371    }
372
373    /**
374     * Set the state property.
375     *
376     * @param state if true then this action will fire ItemEvents
377     */
378    public void setStateAction(boolean state) {
379        putValue(IS_STATE, Boolean.valueOf(state));
380    }
381
382    /**
383     * @return true if the action is in the selected state
384     */
385    public boolean isSelected() {
386        Boolean selected = (Boolean) getValue(SELECTED_KEY);
387        
388        if (selected == null) {
389            return false;
390        }
391        
392        return selected.booleanValue();
393    }
394
395    /**
396     * Changes the state of the action. This is a convenience method for updating the Action via the
397     * value map.
398     * 
399     * @param newValue
400     *            true to set the action as selected of the action.
401     * @see Action#SELECTED_KEY
402     */
403    public void setSelected(boolean newValue) {
404        putValue(SELECTED_KEY, newValue);
405    }
406
407    @Override
408    public String toString() {
409        StringBuffer buffer = new StringBuffer("[");
410        // RG: Fix for J2SE 5.0; Can't cascade append() calls because
411        // return type in StringBuffer and AbstractStringBuilder are different
412        buffer.append(this.getClass().toString());
413        buffer.append(":");
414        try {
415            Object[] keys = getKeys();
416            for (int i = 0; i < keys.length; i++) {
417                buffer.append(keys[i]);
418                buffer.append('=');
419                buffer.append(getValue( (String) keys[i]).toString());
420                if (i < keys.length - 1) {
421                    buffer.append(',');
422                }
423            }
424            buffer.append(']');
425        }
426        catch (Exception ex) {  // RG: append(char) throws IOException in J2SE 5.0
427            /** @todo Log it */
428        }
429        return buffer.toString();
430    }
431    
432    /**
433     * Callback method as <code>ItemListener</code>. Updates internal state based
434     * on the given ItemEvent. <p>
435     * 
436     * Here: synchs selected property if isStateAction(), does nothing otherwise.
437     * 
438     * @param e the ItemEvent fired by a ItemSelectable on changing the selected 
439     *    state.
440     */
441    @Override
442    public void itemStateChanged(ItemEvent e) {
443        if (isStateAction()) {
444            setSelected(ItemEvent.SELECTED == e.getStateChange());
445        }
446    }
447}