Path: blob/master/src/java.desktop/share/classes/javax/swing/AbstractAction.java
41153 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.swing;2627import java.beans.PropertyChangeEvent;28import java.beans.PropertyChangeListener;29import java.io.IOException;30import java.io.ObjectInputStream;31import java.io.ObjectOutputStream;32import java.io.Serial;33import java.io.Serializable;34import java.security.AccessController;3536import javax.swing.event.SwingPropertyChangeSupport;3738import sun.security.action.GetPropertyAction;3940/**41* This class provides default implementations for the JFC <code>Action</code>42* interface. Standard behaviors like the get and set methods for43* <code>Action</code> object properties (icon, text, and enabled) are defined44* here. The developer need only subclass this abstract class and45* define the <code>actionPerformed</code> method.46* <p>47* <strong>Warning:</strong>48* Serialized objects of this class will not be compatible with49* future Swing releases. The current serialization support is50* appropriate for short term storage or RMI between applications running51* the same version of Swing. As of 1.4, support for long term storage52* of all JavaBeans53* has been added to the <code>java.beans</code> package.54* Please see {@link java.beans.XMLEncoder}.55*56* @author Georges Saab57* @see Action58* @since 1.259*/60@SuppressWarnings("serial") // Same-version serialization only61public abstract class AbstractAction implements Action, Cloneable, Serializable62{63/**64* Whether or not actions should reconfigure all properties on null.65*/66private static Boolean RECONFIGURE_ON_NULL;6768/**69* Specifies whether action is enabled; the default is true.70*/71protected boolean enabled = true;727374/**75* Contains the array of key bindings.76*/77private transient ArrayTable arrayTable;7879/**80* Whether or not to reconfigure all action properties from the81* specified event.82*/83@SuppressWarnings("removal")84static boolean shouldReconfigure(PropertyChangeEvent e) {85if (e.getPropertyName() == null) {86synchronized(AbstractAction.class) {87if (RECONFIGURE_ON_NULL == null) {88RECONFIGURE_ON_NULL = Boolean.valueOf(89AccessController.doPrivileged(new GetPropertyAction(90"swing.actions.reconfigureOnNull", "false")));91}92return RECONFIGURE_ON_NULL;93}94}95return false;96}9798/**99* Sets the enabled state of a component from an Action.100*101* @param c the Component to set the enabled state on102* @param a the Action to set the enabled state from, may be null103*/104static void setEnabledFromAction(JComponent c, Action a) {105c.setEnabled((a != null) ? a.isEnabled() : true);106}107108/**109* Sets the tooltip text of a component from an Action.110*111* @param c the Component to set the tooltip text on112* @param a the Action to set the tooltip text from, may be null113*/114static void setToolTipTextFromAction(JComponent c, Action a) {115c.setToolTipText(a != null ?116(String)a.getValue(Action.SHORT_DESCRIPTION) : null);117}118119static boolean hasSelectedKey(Action a) {120return (a != null && a.getValue(Action.SELECTED_KEY) != null);121}122123static boolean isSelected(Action a) {124return Boolean.TRUE.equals(a.getValue(Action.SELECTED_KEY));125}126127128129/**130* Creates an {@code Action}.131*/132public AbstractAction() {133}134135/**136* Creates an {@code Action} with the specified name.137*138* @param name the name ({@code Action.NAME}) for the action; a139* value of {@code null} is ignored140*/141public AbstractAction(String name) {142putValue(Action.NAME, name);143}144145/**146* Creates an {@code Action} with the specified name and small icon.147*148* @param name the name ({@code Action.NAME}) for the action; a149* value of {@code null} is ignored150* @param icon the small icon ({@code Action.SMALL_ICON}) for the action; a151* value of {@code null} is ignored152*/153public AbstractAction(String name, Icon icon) {154this(name);155putValue(Action.SMALL_ICON, icon);156}157158/**159* Gets the <code>Object</code> associated with the specified key.160*161* @param key a string containing the specified <code>key</code>162* @return the binding <code>Object</code> stored with this key; if there163* are no keys, it will return <code>null</code>164* @see Action#getValue165*/166public Object getValue(String key) {167if (key == "enabled") {168return enabled;169}170if (arrayTable == null) {171return null;172}173return arrayTable.get(key);174}175176/**177* Sets the <code>Value</code> associated with the specified key.178*179* @param key the <code>String</code> that identifies the stored object180* @param newValue the <code>Object</code> to store using this key181* @see Action#putValue182*/183public void putValue(String key, Object newValue) {184Object oldValue = null;185if (key == "enabled") {186// Treat putValue("enabled") the same way as a call to setEnabled.187// If we don't do this it means the two may get out of sync, and a188// bogus property change notification would be sent.189//190// To avoid dependencies between putValue & setEnabled this191// directly changes enabled. If we instead called setEnabled192// to change enabled, it would be possible for stack193// overflow in the case where a developer implemented setEnabled194// in terms of putValue.195if (newValue == null || !(newValue instanceof Boolean)) {196newValue = false;197}198oldValue = enabled;199enabled = (Boolean)newValue;200} else {201if (arrayTable == null) {202arrayTable = new ArrayTable();203}204if (arrayTable.containsKey(key))205oldValue = arrayTable.get(key);206// Remove the entry for key if newValue is null207// else put in the newValue for key.208if (newValue == null) {209arrayTable.remove(key);210} else {211arrayTable.put(key,newValue);212}213}214firePropertyChange(key, oldValue, newValue);215}216217/**218* Returns true if the action is enabled.219*220* @return true if the action is enabled, false otherwise221* @see Action#isEnabled222*/223public boolean isEnabled() {224return enabled;225}226227/**228* Sets whether the {@code Action} is enabled. The default is {@code true}.229*230* @param newValue {@code true} to enable the action, {@code false} to231* disable it232* @see Action#setEnabled233*/234public void setEnabled(boolean newValue) {235boolean oldValue = this.enabled;236237if (oldValue != newValue) {238this.enabled = newValue;239firePropertyChange("enabled",240Boolean.valueOf(oldValue), Boolean.valueOf(newValue));241}242}243244245/**246* Returns an array of <code>Object</code>s which are keys for247* which values have been set for this <code>AbstractAction</code>,248* or <code>null</code> if no keys have values set.249* @return an array of key objects, or <code>null</code> if no250* keys have values set251* @since 1.3252*/253public Object[] getKeys() {254if (arrayTable == null) {255return null;256}257Object[] keys = new Object[arrayTable.size()];258arrayTable.getKeys(keys);259return keys;260}261262/**263* If any <code>PropertyChangeListeners</code> have been registered, the264* <code>changeSupport</code> field describes them.265*/266protected SwingPropertyChangeSupport changeSupport;267268/**269* Supports reporting bound property changes. This method can be called270* when a bound property has changed and it will send the appropriate271* <code>PropertyChangeEvent</code> to any registered272* <code>PropertyChangeListeners</code>.273*274* @param propertyName the name of the property that has changed275* @param oldValue the old value of the property276* @param newValue the new value of the property277*/278protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {279if (changeSupport == null ||280(oldValue != null && newValue != null && oldValue.equals(newValue))) {281return;282}283changeSupport.firePropertyChange(propertyName, oldValue, newValue);284}285286287/**288* Adds a <code>PropertyChangeListener</code> to the listener list.289* The listener is registered for all properties.290* <p>291* A <code>PropertyChangeEvent</code> will get fired in response to setting292* a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,293* or <code>setForeground</code>.294* Note that if the current component is inheriting its foreground,295* background, or font from its container, then no event will be296* fired in response to a change in the inherited property.297*298* @param listener The <code>PropertyChangeListener</code> to be added299*300* @see Action#addPropertyChangeListener301*/302public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {303if (changeSupport == null) {304changeSupport = new SwingPropertyChangeSupport(this);305}306changeSupport.addPropertyChangeListener(listener);307}308309310/**311* Removes a <code>PropertyChangeListener</code> from the listener list.312* This removes a <code>PropertyChangeListener</code> that was registered313* for all properties.314*315* @param listener the <code>PropertyChangeListener</code> to be removed316*317* @see Action#removePropertyChangeListener318*/319public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {320if (changeSupport == null) {321return;322}323changeSupport.removePropertyChangeListener(listener);324}325326327/**328* Returns an array of all the <code>PropertyChangeListener</code>s added329* to this AbstractAction with addPropertyChangeListener().330*331* @return all of the <code>PropertyChangeListener</code>s added or an empty332* array if no listeners have been added333* @since 1.4334*/335public synchronized PropertyChangeListener[] getPropertyChangeListeners() {336if (changeSupport == null) {337return new PropertyChangeListener[0];338}339return changeSupport.getPropertyChangeListeners();340}341342343/**344* Clones the abstract action. This gives the clone345* its own copy of the key/value list,346* which is not handled for you by <code>Object.clone()</code>.347**/348349protected Object clone() throws CloneNotSupportedException {350AbstractAction newAction = (AbstractAction)super.clone();351synchronized(this) {352if (arrayTable != null) {353newAction.arrayTable = (ArrayTable)arrayTable.clone();354}355}356return newAction;357}358359@Serial360private void writeObject(ObjectOutputStream s) throws IOException {361// Store the default fields362s.defaultWriteObject();363364// And the keys365ArrayTable.writeArrayTable(s, arrayTable);366}367368@Serial369private void readObject(ObjectInputStream s) throws ClassNotFoundException,370IOException {371s.defaultReadObject();372for (int counter = s.readInt() - 1; counter >= 0; counter--) {373putValue((String)s.readObject(), s.readObject());374}375}376}377378379