/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.util.actions;
import java.beans.PropertyChangeEvent;
import java.util.*;
import java.lang.ref.*;
import org.openide.nodes.Node;
import org.openide.nodes.NodeAdapter;
/** An action
* dependent on the cookies of the selected nodes.
*
* @author Petr Hamernik, Jaroslav Tulach, Dafe Simonek, Jesse Glick
*/
public abstract class CookieAction extends NodeAction {
/** name of property with cookies for this action */
private static final String PROP_COOKIES = "cookies"; // NOI18N
/** Action will be enabled if there are one or more selected nodes
* and there is exactly one node which supports the given cookies. */
public static final int MODE_ONE = 0x01;
/** Action will be enabled if there are several selected nodes
* and some of them (at least one, but not all)
* support the given cookies. */
public static final int MODE_SOME = 0x02;
/** Action will be enabled if there are one or more selected nodes
* and all of them support the given cookies. */
public static final int MODE_ALL = 0x04;
/** Action will be enabled if there is exactly one selected node
* and it supports the given cookies. */
public static final int MODE_EXACTLY_ONE = 0x08;
/** Action will be enabled if there are one or more selected nodes
* and any of them (one, all, or some) support the given cookies. */
public static final int MODE_ANY = 0x07;
// [PENDING] 0x06 should suffice, yes? --jglick
private static final long serialVersionUID =6031319415908298424L;
private CookiesChangeListener listener = new CookiesChangeListener(this);
/** Get the mode of the action: how strict it should be about
* cookie support.
* @return the mode of the action. Possible values are disjunctions of the <code>MODE_XXX</code>
* constants. */
protected abstract int mode();
/** Get the cookies that this action requires. The cookies are disjunctive, i.e. a node
* must support AT LEAST ONE of the cookies specified by this method.
*
* @return a list of cookies
*/
protected abstract Class[] cookieClasses ();
/** Getter for cookies.
* @return the set of cookies for this
*/
private Class[] getCookies () {
Class[] ret = (Class[])getProperty (PROP_COOKIES);
if (ret != null) return ret;
ret = cookieClasses ();
putProperty (PROP_COOKIES, ret);
return ret;
}
/** Test for enablement based on the cookies of selected nodes.
* Generally subclasses should not override this except for strange
* purposes, and then only calling the super method and adding a check.
* Just use {@link #cookieClasses} and {@link #mode} to specify
* the enablement logic.
* @param activatedNodes the set of activated nodes
* @return <code>true</code> to enable
*/
protected boolean enable (Node[] activatedNodes) {
if (activatedNodes.length == 0) {
return false;
}
// sets new nodes to cookie change listener
listener.setNodes(activatedNodes);
// perform enable / disable logic
return doEnable(activatedNodes);
}
/** Helper, actually performs enable / disable logic */
boolean doEnable (Node[] activatedNodes) {
int supported = resolveSupported(activatedNodes);
if (supported == 0)
return false;
int mode = mode ();
return
// [PENDING] shouldn't MODE_ONE also say: && supported == 1? --jglick
((mode & MODE_ONE) != 0) ||
(((mode & MODE_ALL) != 0) && (supported == activatedNodes.length)) ||
(((mode & MODE_EXACTLY_ONE) != 0) && (activatedNodes.length == 1)) ||
(((mode & MODE_SOME) != 0) && (supported < activatedNodes.length));
}
/**
* Implementation of the above method.
*
* @param activatedNodes gives array of actually activated nodes.
* @return number of supported classes
*/
private int resolveSupported (Node[] activatedNodes) {
int total = activatedNodes.length;
int ret = 0;
Class[] cookies = getCookies();
for (int i = 0; i < total; i++) {
for (int j = 0; j < cookies.length; j++) {
// test for supported cookies
if (activatedNodes[i].getCookie(cookies[j]) != null) {
ret++;
break;
}
}
}
return ret;
}
/** Tracks changes of cookie classes in currently selected nodes
*/
private static final class CookiesChangeListener extends NodeAdapter {
/** The nodes we are currently listening */
private List nodes;
/** the associated action */
private Reference action; // Reference<CookieAction>
/** Constructor - asociates with given cookie action
*/
public CookiesChangeListener(CookieAction a) {
action = new WeakReference(a);
}
/** Sets the nodes to work on */
void setNodes (Node[] newNodes) {
// detach old nodes
List nodes2 = this.nodes;
if (nodes2 != null) {
detachListeners(nodes2);
}
nodes = null;
// attach to new nodes
if (newNodes != null) {
nodes = new ArrayList (newNodes.length);
for (int i = 0; i < newNodes.length; i++)
nodes.add(new WeakReference (newNodes[i]));
attachListeners(nodes);
}
}
/** Removes itself as a listener from given nodes */
void detachListeners (List nodes) {
Iterator it = nodes.iterator();
while (it.hasNext()) {
Node node = (Node)((Reference)it.next()).get ();
if (node != null)
node.removeNodeListener(this);
}
}
/** Attach itself as a listener to the given nodes */
void attachListeners (List nodes) {
Iterator it = nodes.iterator();
while (it.hasNext()) {
Node node = (Node)((Reference)it.next()).get ();
if (node != null)
node.addNodeListener(this);
}
}
/** Reacts to the cookie classes change -
* calls enable on asociated action */
public void propertyChange (PropertyChangeEvent ev) {
// filter only cookie classes changes
if (!Node.PROP_COOKIE.equals(ev.getPropertyName()))
return;
// find asociated action
CookieAction a = (CookieAction)action.get();
if (a == null) return;
List _nodes = this.nodes;
if (_nodes != null) {
ArrayList nonNullNodes = new ArrayList (_nodes.size());
Iterator it = _nodes.iterator();
while (it.hasNext()) {
Node node = (Node)((Reference)it.next()).get ();
if (node != null)
nonNullNodes.add (node);
else
// If there is really a selection, it should not have been collected.
return;
}
Node[] nodes2 = new Node [nonNullNodes.size()];
nonNullNodes.toArray(nodes2);
a.setEnabled (a.enable (nodes2));
}
}
} // end of CookiesChangeListener
}