1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing; 26 27 import java.awt.*; 28 import java.awt.event.*; 29 import java.beans.*; 30 import java.util.Hashtable; 31 import java.util.Enumeration; 32 import java.io.Serializable; 33 import java.io.IOException; 34 import java.io.ObjectInputStream; 35 import java.io.ObjectOutputStream; 36 import java.security.AccessController; 37 import javax.swing.event.SwingPropertyChangeSupport; 38 import 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™ 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 * @since 1.2 59 */ 60 @SuppressWarnings("serial") // Same-version serialization only 61 public abstract class AbstractAction implements Action, Cloneable, Serializable 62 { 63 /** 64 * Whether or not actions should reconfigure all properties on null. 65 */ 66 private static Boolean RECONFIGURE_ON_NULL; 67 68 /** 69 * Specifies whether action is enabled; the default is true. 70 */ 71 protected boolean enabled = true; 72 73 74 /** 75 * Contains the array of key bindings. 76 */ 77 private transient ArrayTable arrayTable; 78 79 /** 80 * Whether or not to reconfigure all action properties from the 81 * specified event. 82 */ 83 static boolean shouldReconfigure(PropertyChangeEvent e) { 84 if (e.getPropertyName() == null) { 85 synchronized(AbstractAction.class) { 86 if (RECONFIGURE_ON_NULL == null) { 87 RECONFIGURE_ON_NULL = Boolean.valueOf( 88 AccessController.doPrivileged(new GetPropertyAction( 89 "swing.actions.reconfigureOnNull", "false"))); 90 } 91 return RECONFIGURE_ON_NULL; 92 } 93 } 94 return false; 95 } 96 97 /** 98 * Sets the enabled state of a component from an Action. 99 * 100 * @param c the Component to set the enabled state on 101 * @param a the Action to set the enabled state from, may be null 102 */ 103 static void setEnabledFromAction(JComponent c, Action a) { 104 c.setEnabled((a != null) ? a.isEnabled() : true); 105 } 106 107 /** 108 * Sets the tooltip text of a component from an Action. 109 * 110 * @param c the Component to set the tooltip text on 111 * @param a the Action to set the tooltip text from, may be null 112 */ 113 static void setToolTipTextFromAction(JComponent c, Action a) { 114 c.setToolTipText(a != null ? 115 (String)a.getValue(Action.SHORT_DESCRIPTION) : null); 116 } 117 118 static boolean hasSelectedKey(Action a) { 119 return (a != null && a.getValue(Action.SELECTED_KEY) != null); 120 } 121 122 static boolean isSelected(Action a) { 123 return Boolean.TRUE.equals(a.getValue(Action.SELECTED_KEY)); 124 } 125 126 127 128 /** 129 * Creates an {@code Action}. 130 */ 131 public AbstractAction() { 132 } 133 134 /** 135 * Creates an {@code Action} with the specified name. 136 * 137 * @param name the name ({@code Action.NAME}) for the action; a 138 * value of {@code null} is ignored 139 */ 140 public AbstractAction(String name) { 141 putValue(Action.NAME, name); 142 } 143 144 /** 145 * Creates an {@code Action} with the specified name and small icon. 146 * 147 * @param name the name ({@code Action.NAME}) for the action; a 148 * value of {@code null} is ignored 149 * @param icon the small icon ({@code Action.SMALL_ICON}) for the action; a 150 * value of {@code null} is ignored 151 */ 152 public AbstractAction(String name, Icon icon) { 153 this(name); 154 putValue(Action.SMALL_ICON, icon); 155 } 156 157 /** 158 * Gets the <code>Object</code> associated with the specified key. 159 * 160 * @param key a string containing the specified <code>key</code> 161 * @return the binding <code>Object</code> stored with this key; if there 162 * are no keys, it will return <code>null</code> 163 * @see Action#getValue 164 */ 165 public Object getValue(String key) { 166 if (key == "enabled") { 167 return enabled; 168 } 169 if (arrayTable == null) { 170 return null; 171 } 172 return arrayTable.get(key); 173 } 174 175 /** 176 * Sets the <code>Value</code> associated with the specified key. 177 * 178 * @param key the <code>String</code> that identifies the stored object 179 * @param newValue the <code>Object</code> to store using this key 180 * @see Action#putValue 181 */ 182 public void putValue(String key, Object newValue) { 183 Object oldValue = null; 184 if (key == "enabled") { 185 // Treat putValue("enabled") the same way as a call to setEnabled. 186 // If we don't do this it means the two may get out of sync, and a 187 // bogus property change notification would be sent. 188 // 189 // To avoid dependencies between putValue & setEnabled this 190 // directly changes enabled. If we instead called setEnabled 191 // to change enabled, it would be possible for stack 192 // overflow in the case where a developer implemented setEnabled 193 // in terms of putValue. 194 if (newValue == null || !(newValue instanceof Boolean)) { 195 newValue = false; 196 } 197 oldValue = enabled; 198 enabled = (Boolean)newValue; 199 } else { 200 if (arrayTable == null) { 201 arrayTable = new ArrayTable(); 202 } 203 if (arrayTable.containsKey(key)) 204 oldValue = arrayTable.get(key); 205 // Remove the entry for key if newValue is null 206 // else put in the newValue for key. 207 if (newValue == null) { 208 arrayTable.remove(key); 209 } else { 210 arrayTable.put(key,newValue); 211 } 212 } 213 firePropertyChange(key, oldValue, newValue); 214 } 215 216 /** 217 * Returns true if the action is enabled. 218 * 219 * @return true if the action is enabled, false otherwise 220 * @see Action#isEnabled 221 */ 222 public boolean isEnabled() { 223 return enabled; 224 } 225 226 /** 227 * Sets whether the {@code Action} is enabled. The default is {@code true}. 228 * 229 * @param newValue {@code true} to enable the action, {@code false} to 230 * disable it 231 * @see Action#setEnabled 232 */ 233 public void setEnabled(boolean newValue) { 234 boolean oldValue = this.enabled; 235 236 if (oldValue != newValue) { 237 this.enabled = newValue; 238 firePropertyChange("enabled", 239 Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); 240 } 241 } 242 243 244 /** 245 * Returns an array of <code>Object</code>s which are keys for 246 * which values have been set for this <code>AbstractAction</code>, 247 * or <code>null</code> if no keys have values set. 248 * @return an array of key objects, or <code>null</code> if no 249 * keys have values set 250 * @since 1.3 251 */ 252 public Object[] getKeys() { 253 if (arrayTable == null) { 254 return null; 255 } 256 Object[] keys = new Object[arrayTable.size()]; 257 arrayTable.getKeys(keys); 258 return keys; 259 } 260 261 /** 262 * If any <code>PropertyChangeListeners</code> have been registered, the 263 * <code>changeSupport</code> field describes them. 264 */ 265 protected SwingPropertyChangeSupport changeSupport; 266 267 /** 268 * Supports reporting bound property changes. This method can be called 269 * when a bound property has changed and it will send the appropriate 270 * <code>PropertyChangeEvent</code> to any registered 271 * <code>PropertyChangeListeners</code>. 272 */ 273 protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 274 if (changeSupport == null || 275 (oldValue != null && newValue != null && oldValue.equals(newValue))) { 276 return; 277 } 278 changeSupport.firePropertyChange(propertyName, oldValue, newValue); 279 } 280 281 282 /** 283 * Adds a <code>PropertyChangeListener</code> to the listener list. 284 * The listener is registered for all properties. 285 * <p> 286 * A <code>PropertyChangeEvent</code> will get fired in response to setting 287 * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>, 288 * or <code>setForeground</code>. 289 * Note that if the current component is inheriting its foreground, 290 * background, or font from its container, then no event will be 291 * fired in response to a change in the inherited property. 292 * 293 * @param listener The <code>PropertyChangeListener</code> to be added 294 * 295 * @see Action#addPropertyChangeListener 296 */ 297 public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { 298 if (changeSupport == null) { 299 changeSupport = new SwingPropertyChangeSupport(this); 300 } 301 changeSupport.addPropertyChangeListener(listener); 302 } 303 304 305 /** 306 * Removes a <code>PropertyChangeListener</code> from the listener list. 307 * This removes a <code>PropertyChangeListener</code> that was registered 308 * for all properties. 309 * 310 * @param listener the <code>PropertyChangeListener</code> to be removed 311 * 312 * @see Action#removePropertyChangeListener 313 */ 314 public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { 315 if (changeSupport == null) { 316 return; 317 } 318 changeSupport.removePropertyChangeListener(listener); 319 } 320 321 322 /** 323 * Returns an array of all the <code>PropertyChangeListener</code>s added 324 * to this AbstractAction with addPropertyChangeListener(). 325 * 326 * @return all of the <code>PropertyChangeListener</code>s added or an empty 327 * array if no listeners have been added 328 * @since 1.4 329 */ 330 public synchronized PropertyChangeListener[] getPropertyChangeListeners() { 331 if (changeSupport == null) { 332 return new PropertyChangeListener[0]; 333 } 334 return changeSupport.getPropertyChangeListeners(); 335 } 336 337 338 /** 339 * Clones the abstract action. This gives the clone 340 * its own copy of the key/value list, 341 * which is not handled for you by <code>Object.clone()</code>. 342 **/ 343 344 protected Object clone() throws CloneNotSupportedException { 345 AbstractAction newAction = (AbstractAction)super.clone(); 346 synchronized(this) { 347 if (arrayTable != null) { 348 newAction.arrayTable = (ArrayTable)arrayTable.clone(); 349 } 350 } 351 return newAction; 352 } 353 354 private void writeObject(ObjectOutputStream s) throws IOException { 355 // Store the default fields 356 s.defaultWriteObject(); 357 358 // And the keys 359 ArrayTable.writeArrayTable(s, arrayTable); 360 } 361 362 private void readObject(ObjectInputStream s) throws ClassNotFoundException, 363 IOException { 364 s.defaultReadObject(); 365 for (int counter = s.readInt() - 1; counter >= 0; counter--) { 366 putValue((String)s.readObject(), s.readObject()); 367 } 368 } 369 }