blob: 3db7f0365365381f27fea7851ab478e97a13b885 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.spi.actions;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Action;
import org.openide.util.ContextAwareAction;
import org.openide.util.Lookup;
/**
* Base class for context aware actions. Adds notification of first/last
* listener attachment to ContextAwareAction,
*
* @author Tim Boudreau
*/
public abstract class NbAction implements ContextAwareAction {
private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
final Map <String, Object> pairs = Collections.synchronizedMap (new HashMap<String, Object>());
private final Object STATE_LOCK = new Object();
private volatile boolean attached;
public static final String PROP_ENABLED = "enabled";
boolean attached() {
return attached;
}
Object lock() {
return STATE_LOCK;
}
/**
* Called when the first listener (such as a UI component) is added
* to this action. If your action depends on some external property
* to compute its enabled state, override this method to add listeners.
*/
protected void addNotify() {
//do nothing
}
/**
* Called when the last listener (such as a UI component) is removed
* to this action. If your action depends on some external property
* to compute its enabled state, override this method to detach listeners.
*/
protected void removeNotify() {
//do nothing
}
void internalAddNotify() {
addNotify();
}
void internalRemoveNotify() {
removeNotify();
}
/**
* Fire a property change
* @param name The property name
* @param old The old value
* @param nue The new value
*/
protected final void firePropertyChange(final String name, final Object old, final Object nue) {
supp.firePropertyChange(name, old, nue);
}
interface ActionRunnable<T> {
//A bit of ugliness to accomplish two things:
// - Cannot have a common supertype for ActionStub + ContextAction
// without exporting it to clients
// - Need to run some code on an uninitialized ActionStub or
// contextAction *as if* it were being listened to
T run(NbAction a);
}
/**
* Run an ActionRunnable against the passed action. This method first
* invokes addNotify() if necessary, and then removeNotify() if
* addNotify() was called, before running the ActionRunnable. This
* ensure that the action's state is as if it were in use even if it
* is not actually, so that correct values can be returned for an
* action that is not in use.
* @param <T> The return type
* @param ar A runnable that will call something on the action
* @param a The action
* @return The return value of the ActionRunnable
*/
<T> T runActive(ActionRunnable<T> ar, NbAction a) {
boolean wasActive = a.attached();
try {
if (!wasActive) {
a.internalAddNotify();
}
return ar.run(a);
} finally {
if (!wasActive) {
a.internalRemoveNotify();
}
}
}
public Object getValue(String key) {
return pairs.get(key);
}
public void putValue(String key, Object value) {
Object old = pairs.put(key, value);
boolean fire = (old == null) != (value == null);
if (fire) {
fire = (value != null && !value.equals(old)) ||
(old != null && old.equals(value));
if (fire) {
supp.firePropertyChange(key, old, value);
}
}
}
/**
* setEnabled() is a mistake in the Swing Action API - it should not be
* there.
* Overridden to throw an exception. Do not call.
* @param b
*/
public final void setEnabled(boolean b) {
throw new UnsupportedOperationException("Illegal");
}
public final void addPropertyChangeListener(PropertyChangeListener listener) {
supp.addPropertyChangeListener(listener);
if (supp.getPropertyChangeListeners().length == 1) {
attached = true;
internalAddNotify();
}
}
public final void removePropertyChangeListener(PropertyChangeListener listener) {
supp.removePropertyChangeListener(listener);
if (supp.getPropertyChangeListeners().length == 0) {
attached = false;
internalRemoveNotify();
}
}
/**
* Create an instance over a specific context, for subclasses which are
* context-aware (their enabled state depends on the current selection).
* Calls <code>internalCreateContextAwareInstance()</code> to ensure
* that the instance returned is a subclass of NbAction, not just an
* implementation of ContextAwareAction.
*
* @param actionContext A selection context
* @return An action
*/
public final Action createContextAwareInstance(Lookup actionContext) {
return internalCreateContextAwareInstance(actionContext);
}
/**
* Create a context sensitive instance of this action over a specific
* action context.
* The default implementation of this method return <code>this</code> (i.e.
* no context sensitivity whatsoever).
* It should be overridden if the subclass is actually context sensitive.
* @param actionContext
* @return this
*/
protected NbAction internalCreateContextAwareInstance(Lookup actionContext) {
return this;
}
/**
* Create an action that merges several <code>ContextAction</code>s. This
* is useful, for example, if you want to create a global action which is enabled
* if the user has, say, selected a Project, <i>or</i> if
* the selected Node is owned by a Project. Instead of writing one
* action with complex enablement logic, you write one action which is
* sensitive to Nodes (or DataObjects, or whatever) and one which is
* directly sensitive to Projects. Each has its own fairly simple
* enablement logic.
* <p/>
* Since this action merges multiple actions, some rules apply as far
* as which action gets called when and for what, in the case of display
* names and enablement status. This works as follows:
* <ul>
* <li>If one of the actions in the array is enabled
* <ul>
* <li>The returned ContextAwareAction is enabled</li>
* <li>The first enabled action in the array supplies the return
* values for calls to <code>getValue("someKey")</code> - i.e. the
* first enabled action controls the display name, icon, etc.
* If the first enabled action returns null from <code>getValue()</code>,
* the next enabled action is tried, and so forth, until there
* is a non-null result. If there is no enabled action which
* returns non-null from <code>getValue()</code>, then the first
* non-null value returned by any action in the array, starting
* with the first, is used.
* </li>
* </ul>
* </li>
* </li>
* </ul>
*
* @param actions An array of ContextActions.
* @param exclusive If true, the resulting action will be <i>disabled</i> if
* more than one of the passed actions is <i>enabled</i>. This is sometimes
* useful, for example, if performing the same action over very disparate
* types of object (say, closing both a file and a project) would be
* non-intuitive.
* @return An action.
*/
public static NbAction merge (boolean exclusive, NbAction... actions) {
return new MergeAction(actions, exclusive);
}
/**
* Same as merge (actions, false);
*
* @param actions An array of context actions
* @return An action which merges the passed actions
*/
public static NbAction merge (NbAction... actions) {
return new MergeAction(actions);
}
}