blob: 0d0c190fd5925bdc793143fbf5db91b52b7ed430 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing;
26
27import java.awt.*;
28import java.awt.event.*;
29import java.beans.*;
30import java.util.Hashtable;
31import java.util.Enumeration;
32import java.io.Serializable;
33import java.io.IOException;
34import java.io.ObjectInputStream;
35import java.io.ObjectOutputStream;
36import java.security.AccessController;
37import javax.swing.event.SwingPropertyChangeSupport;
38import sun.security.action.GetPropertyAction;
39
40/**
41 * This class provides default implementations for the JFC <code>Action</code>
42 * interface. Standard behaviors like the get and set methods for
43 * <code>Action</code> object properties (icon, text, and enabled) are defined
44 * here. The developer need only subclass this abstract class and
45 * define the <code>actionPerformed</code> method.
46 * <p>
47 * <strong>Warning:</strong>
48 * Serialized objects of this class will not be compatible with
49 * future Swing releases. The current serialization support is
50 * appropriate for short term storage or RMI between applications running
51 * the same version of Swing. As of 1.4, support for long term storage
52 * of all JavaBeans<sup><font size="-2">TM</font></sup>
53 * has been added to the <code>java.beans</code> package.
54 * Please see {@link java.beans.XMLEncoder}.
55 *
56 * @author Georges Saab
57 * @see Action
58 */
59public abstract class AbstractAction implements Action, Cloneable, Serializable
60{
61 /**
62 * Whether or not actions should reconfigure all properties on null.
63 */
64 private static Boolean RECONFIGURE_ON_NULL;
65
66 /**
67 * Specifies whether action is enabled; the default is true.
68 */
69 protected boolean enabled = true;
70
71
72 /**
73 * Contains the array of key bindings.
74 */
75 private transient ArrayTable arrayTable;
76
77 /**
78 * Whether or not to reconfigure all action properties from the
79 * specified event.
80 */
81 static boolean shouldReconfigure(PropertyChangeEvent e) {
82 if (e.getPropertyName() == null) {
83 synchronized(AbstractAction.class) {
84 if (RECONFIGURE_ON_NULL == null) {
85 RECONFIGURE_ON_NULL = Boolean.valueOf(
86 AccessController.doPrivileged(new GetPropertyAction(
87 "swing.actions.reconfigureOnNull", "false")));
88 }
89 return RECONFIGURE_ON_NULL;
90 }
91 }
92 return false;
93 }
94
95 /**
96 * Sets the enabled state of a component from an Action.
97 *
98 * @param c the Component to set the enabled state on
99 * @param a the Action to set the enabled state from, may be null
100 */
101 static void setEnabledFromAction(JComponent c, Action a) {
102 c.setEnabled((a != null) ? a.isEnabled() : true);
103 }
104
105 /**
106 * Sets the tooltip text of a component from an Action.
107 *
108 * @param c the Component to set the tooltip text on
109 * @param a the Action to set the tooltip text from, may be null
110 */
111 static void setToolTipTextFromAction(JComponent c, Action a) {
112 c.setToolTipText(a != null ?
113 (String)a.getValue(Action.SHORT_DESCRIPTION) : null);
114 }
115
116 static boolean hasSelectedKey(Action a) {
117 return (a != null && a.getValue(Action.SELECTED_KEY) != null);
118 }
119
120 static boolean isSelected(Action a) {
121 return Boolean.TRUE.equals(a.getValue(Action.SELECTED_KEY));
122 }
123
124
125
126 /**
127 * Creates an {@code Action}.
128 */
129 public AbstractAction() {
130 }
131
132 /**
133 * Creates an {@code Action} with the specified name.
134 *
135 * @param name the name ({@code Action.NAME}) for the action; a
136 * value of {@code null} is ignored
137 */
138 public AbstractAction(String name) {
139 putValue(Action.NAME, name);
140 }
141
142 /**
143 * Creates an {@code Action} with the specified name and small icon.
144 *
145 * @param name the name ({@code Action.NAME}) for the action; a
146 * value of {@code null} is ignored
147 * @param icon the small icon ({@code Action.SMALL_ICON}) for the action; a
148 * value of {@code null} is ignored
149 */
150 public AbstractAction(String name, Icon icon) {
151 this(name);
152 putValue(Action.SMALL_ICON, icon);
153 }
154
155 /**
156 * Gets the <code>Object</code> associated with the specified key.
157 *
158 * @param key a string containing the specified <code>key</code>
159 * @return the binding <code>Object</code> stored with this key; if there
160 * are no keys, it will return <code>null</code>
161 * @see Action#getValue
162 */
163 public Object getValue(String key) {
164 if (key == "enabled") {
165 return enabled;
166 }
167 if (arrayTable == null) {
168 return null;
169 }
170 return arrayTable.get(key);
171 }
172
173 /**
174 * Sets the <code>Value</code> associated with the specified key.
175 *
176 * @param key the <code>String</code> that identifies the stored object
177 * @param newValue the <code>Object</code> to store using this key
178 * @see Action#putValue
179 */
180 public void putValue(String key, Object newValue) {
181 Object oldValue = null;
182 if (key == "enabled") {
183 // Treat putValue("enabled") the same way as a call to setEnabled.
184 // If we don't do this it means the two may get out of sync, and a
185 // bogus property change notification would be sent.
186 //
187 // To avoid dependencies between putValue & setEnabled this
188 // directly changes enabled. If we instead called setEnabled
189 // to change enabled, it would be possible for stack
190 // overflow in the case where a developer implemented setEnabled
191 // in terms of putValue.
192 if (newValue == null || !(newValue instanceof Boolean)) {
193 newValue = false;
194 }
195 oldValue = enabled;
196 enabled = (Boolean)newValue;
197 } else {
198 if (arrayTable == null) {
199 arrayTable = new ArrayTable();
200 }
201 if (arrayTable.containsKey(key))
202 oldValue = arrayTable.get(key);
203 // Remove the entry for key if newValue is null
204 // else put in the newValue for key.
205 if (newValue == null) {
206 arrayTable.remove(key);
207 } else {
208 arrayTable.put(key,newValue);
209 }
210 }
211 firePropertyChange(key, oldValue, newValue);
212 }
213
214 /**
215 * Returns true if the action is enabled.
216 *
217 * @return true if the action is enabled, false otherwise
218 * @see Action#isEnabled
219 */
220 public boolean isEnabled() {
221 return enabled;
222 }
223
224 /**
225 * Sets whether the {@code Action} is enabled. The default is {@code true}.
226 *
227 * @param newValue {@code true} to enable the action, {@code false} to
228 * disable it
229 * @see Action#setEnabled
230 */
231 public void setEnabled(boolean newValue) {
232 boolean oldValue = this.enabled;
233
234 if (oldValue != newValue) {
235 this.enabled = newValue;
236 firePropertyChange("enabled",
237 Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
238 }
239 }
240
241
242 /**
243 * Returns an array of <code>Object</code>s which are keys for
244 * which values have been set for this <code>AbstractAction</code>,
245 * or <code>null</code> if no keys have values set.
246 * @return an array of key objects, or <code>null</code> if no
247 * keys have values set
248 * @since 1.3
249 */
250 public Object[] getKeys() {
251 if (arrayTable == null) {
252 return null;
253 }
254 Object[] keys = new Object[arrayTable.size()];
255 arrayTable.getKeys(keys);
256 return keys;
257 }
258
259 /**
260 * If any <code>PropertyChangeListeners</code> have been registered, the
261 * <code>changeSupport</code> field describes them.
262 */
263 protected SwingPropertyChangeSupport changeSupport;
264
265 /**
266 * Supports reporting bound property changes. This method can be called
267 * when a bound property has changed and it will send the appropriate
268 * <code>PropertyChangeEvent</code> to any registered
269 * <code>PropertyChangeListeners</code>.
270 */
271 protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
272 if (changeSupport == null ||
273 (oldValue != null && newValue != null && oldValue.equals(newValue))) {
274 return;
275 }
276 changeSupport.firePropertyChange(propertyName, oldValue, newValue);
277 }
278
279
280 /**
281 * Adds a <code>PropertyChangeListener</code> to the listener list.
282 * The listener is registered for all properties.
283 * <p>
284 * A <code>PropertyChangeEvent</code> will get fired in response to setting
285 * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,
286 * or <code>setForeground</code>.
287 * Note that if the current component is inheriting its foreground,
288 * background, or font from its container, then no event will be
289 * fired in response to a change in the inherited property.
290 *
291 * @param listener The <code>PropertyChangeListener</code> to be added
292 *
293 * @see Action#addPropertyChangeListener
294 */
295 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
296 if (changeSupport == null) {
297 changeSupport = new SwingPropertyChangeSupport(this);
298 }
299 changeSupport.addPropertyChangeListener(listener);
300 }
301
302
303 /**
304 * Removes a <code>PropertyChangeListener</code> from the listener list.
305 * This removes a <code>PropertyChangeListener</code> that was registered
306 * for all properties.
307 *
308 * @param listener the <code>PropertyChangeListener</code> to be removed
309 *
310 * @see Action#removePropertyChangeListener
311 */
312 public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
313 if (changeSupport == null) {
314 return;
315 }
316 changeSupport.removePropertyChangeListener(listener);
317 }
318
319
320 /**
321 * Returns an array of all the <code>PropertyChangeListener</code>s added
322 * to this AbstractAction with addPropertyChangeListener().
323 *
324 * @return all of the <code>PropertyChangeListener</code>s added or an empty
325 * array if no listeners have been added
326 * @since 1.4
327 */
328 public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
329 if (changeSupport == null) {
330 return new PropertyChangeListener[0];
331 }
332 return changeSupport.getPropertyChangeListeners();
333 }
334
335
336 /**
337 * Clones the abstract action. This gives the clone
338 * its own copy of the key/value list,
339 * which is not handled for you by <code>Object.clone()</code>.
340 **/
341
342 protected Object clone() throws CloneNotSupportedException {
343 AbstractAction newAction = (AbstractAction)super.clone();
344 synchronized(this) {
345 if (arrayTable != null) {
346 newAction.arrayTable = (ArrayTable)arrayTable.clone();
347 }
348 }
349 return newAction;
350 }
351
352 private void writeObject(ObjectOutputStream s) throws IOException {
353 // Store the default fields
354 s.defaultWriteObject();
355
356 // And the keys
357 ArrayTable.writeArrayTable(s, arrayTable);
358 }
359
360 private void readObject(ObjectInputStream s) throws ClassNotFoundException,
361 IOException {
362 s.defaultReadObject();
363 for (int counter = s.readInt() - 1; counter >= 0; counter--) {
364 putValue((String)s.readObject(), s.readObject());
365 }
366 }
367}