| /* |
| * 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); |
| } |
| } |