2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
41
package org.openide.util.actions;
43
import org.openide.nodes.Node;
44
import org.openide.nodes.NodeAdapter;
45
import org.openide.nodes.NodeListener;
46
import org.openide.util.Lookup;
47
import org.openide.util.Mutex;
49
import java.beans.PropertyChangeEvent;
51
import java.lang.ref.*;
57
* dependent on the cookies of the selected nodes.
59
* @author Petr Hamernik, Jaroslav Tulach, Dafe Simonek, Jesse Glick
61
public abstract class CookieAction extends NodeAction {
62
/** name of property with cookies for this action */
63
private static final String PROP_COOKIES = "cookies"; // NOI18N
65
/** Action will be enabled if there are one or more selected nodes
66
* and there is exactly one node which supports the given cookies. */
67
public static final int MODE_ONE = 0x01;
69
/** Action will be enabled if there are several selected nodes
70
* and some of them (at least one, but not all)
71
* support the given cookies. */
72
public static final int MODE_SOME = 0x02;
74
/** Action will be enabled if there are one or more selected nodes
75
* and all of them support the given cookies. */
76
public static final int MODE_ALL = 0x04;
78
/** Action will be enabled if there is exactly one selected node
79
* and it supports the given cookies. */
80
public static final int MODE_EXACTLY_ONE = 0x08;
82
/** Action will be enabled if there are one or more selected nodes
83
* and any of them (one, all, or some) support the given cookies. */
84
public static final int MODE_ANY = 0x07;
86
// [PENDING] 0x06 should suffice, yes? --jglick
87
private static final long serialVersionUID = 6031319415908298424L;
88
private CookiesChangeListener listener = new CookiesChangeListener(this);
90
/** Get the mode of the action: how strict it should be about
92
* @return the mode of the action. Possible values are disjunctions of the <code>MODE_XXX</code>
94
protected abstract int mode();
96
/** Get the cookies that this action requires. The cookies are disjunctive, i.e. a node
97
* must support AT LEAST ONE of the cookies specified by this method.
99
* @return a list of cookies
101
protected abstract Class<?>[] cookieClasses(); // might not extend Node.Cookie; seems to work with Lookup
103
/** Getter for cookies.
104
* @return the set of cookies for this
106
private Class<?>[] getCookies() {
107
Class[] ret = (Class[]) getProperty(PROP_COOKIES);
113
ret = cookieClasses();
114
putProperty(PROP_COOKIES, ret);
119
/** Test for enablement based on the cookies of selected nodes.
120
* Generally subclasses should not override this except for strange
121
* purposes, and then only calling the super method and adding a check.
122
* Just use {@link #cookieClasses} and {@link #mode} to specify
123
* the enablement logic.
124
* @param activatedNodes the set of activated nodes
125
* @return <code>true</code> to enable
127
protected boolean enable(Node[] activatedNodes) {
128
if (activatedNodes.length == 0) {
132
// sets new nodes to cookie change listener
133
listener.setNodes(activatedNodes);
135
// perform enable / disable logic
136
return doEnable(activatedNodes);
139
/** Implements <code>ContextAwareAction</code> interface method. */
140
public javax.swing.Action createContextAwareInstance(Lookup actionContext) {
141
return new CookieDelegateAction(this, actionContext);
144
/** Helper, actually performs enable / disable logic */
145
boolean doEnable(Node[] activatedNodes) {
146
int supported = resolveSupported(activatedNodes);
148
if (supported == 0) {
155
// [PENDING] shouldn't MODE_ONE also say: && supported == 1? --jglick
156
((mode & MODE_ONE) != 0) || (((mode & MODE_ALL) != 0) && (supported == activatedNodes.length)) ||
157
(((mode & MODE_EXACTLY_ONE) != 0) && (activatedNodes.length == 1)) ||
158
(((mode & MODE_SOME) != 0) && (supported < activatedNodes.length));
162
* Implementation of the above method.
164
* @param activatedNodes gives array of actually activated nodes.
165
* @return number of supported classes
167
private int resolveSupported(Node[] activatedNodes) {
170
Class<?>[] cookies = getCookies();
172
for (Node n : activatedNodes) {
173
for (Class<?> cookie : cookies) {
174
// test for supported cookies
175
@SuppressWarnings("unchecked")
176
Lookup.Template<?> templ = new Lookup.Template(cookie);
177
if (n.getLookup().lookupItem(templ) != null) {
188
/** Tracks changes of cookie classes in currently selected nodes
190
private static final class CookiesChangeListener extends NodeAdapter {
191
/** our weak listener */
192
private org.openide.nodes.NodeListener listener;
194
/** The nodes we are currently listening */
195
private List<Reference<Node>> nodes;
197
/** the associated action */
198
private Reference<CookieAction> action;
200
/** Constructor - asociates with given cookie action
202
public CookiesChangeListener(CookieAction a) {
203
listener = org.openide.nodes.NodeOp.weakNodeListener(this, null);
204
action = new WeakReference<CookieAction>(a);
207
/** Sets the nodes to work on */
208
void setNodes(Node[] newNodes) {
210
List<Reference<Node>> nodes2 = this.nodes;
212
if (nodes2 != null) {
213
detachListeners(nodes2);
218
// attach to new nodes
219
if (newNodes != null) {
220
nodes = new ArrayList<Reference<Node>>(newNodes.length);
222
for (int i = 0; i < newNodes.length; i++)
223
nodes.add(new WeakReference<Node>(newNodes[i]));
225
attachListeners(nodes);
229
/** Removes itself as a listener from given nodes */
230
void detachListeners(List<Reference<Node>> nodes) {
231
Iterator<Reference<Node>> it = nodes.iterator();
233
while (it.hasNext()) {
234
Node node = it.next().get();
237
node.removeNodeListener(listener);
242
/** Attach itself as a listener to the given nodes */
243
void attachListeners(List<Reference<Node>> nodes) {
244
Iterator<Reference<Node>> it = nodes.iterator();
246
while (it.hasNext()) {
247
Node node = it.next().get();
250
node.addNodeListener(listener);
255
/** Reacts to the cookie classes change -
256
* calls enable on asociated action */
257
public void propertyChange(PropertyChangeEvent ev) {
258
// filter only cookie classes changes
259
if (!Node.PROP_COOKIE.equals(ev.getPropertyName())) {
263
// find asociated action
264
final CookieAction a = action.get();
270
List<Reference<Node>> _nodes = this.nodes;
272
if (_nodes != null) {
273
ArrayList<Node> nonNullNodes = new ArrayList<Node>(_nodes.size());
274
Iterator<Reference<Node>> it = _nodes.iterator();
276
while (it.hasNext()) {
277
Node node = it.next().get();
280
nonNullNodes.add(node);
282
// If there is really a selection, it should not have been collected.
287
final Node[] nodes2 = new Node[nonNullNodes.size()];
288
nonNullNodes.toArray(nodes2);
290
Mutex.EVENT.writeAccess(
293
a.setEnabled(a.enable(nodes2));
300
protected void finalize() {
301
detachListeners(nodes);
304
// end of CookiesChangeListener
306
/** A delegate action that is usually associated with a specific lookup and
307
* extract the nodes it operates on from it. Otherwise it delegates to the
308
* regular NodeAction.
310
final static class CookieDelegateAction extends org.openide.util.actions.NodeAction.DelegateAction
311
implements org.openide.nodes.NodeListener, Runnable {
312
/** our weak listener */
313
private org.openide.nodes.NodeListener listener;
315
/** The nodes we are currently listening */
316
private List<Reference<Node>> nodes;
318
public CookieDelegateAction(CookieAction a, Lookup actionContext) {
319
super(a, actionContext);
320
listener = org.openide.nodes.NodeOp.weakNodeListener(this, null);
324
public void resultChanged(org.openide.util.LookupEvent ev) {
326
superResultChanged(ev);
329
private void superResultChanged(org.openide.util.LookupEvent ev) {
330
super.resultChanged(ev);
333
public void childrenAdded(org.openide.nodes.NodeMemberEvent ev) {
336
public void childrenRemoved(org.openide.nodes.NodeMemberEvent ev) {
339
public void childrenReordered(org.openide.nodes.NodeReorderEvent ev) {
342
public void nodeDestroyed(org.openide.nodes.NodeEvent ev) {
345
public void propertyChange(PropertyChangeEvent ev) {
346
// filter only cookie classes changes
347
if (!Node.PROP_COOKIE.equals(ev.getPropertyName())) {
351
// find asociated action
352
Mutex.EVENT.readAccess(this);
356
superResultChanged(null);
359
/** Attach itself as a listener own nodes */
360
private void setNodes(org.openide.nodes.Node[] newNodes) {
361
// detach listeners from old nodes
362
detachListeners(nodes);
364
// attach to new nodes
365
if (newNodes != null) {
366
nodes = new ArrayList<Reference<Node>>(newNodes.length);
368
for (int i = 0; i < newNodes.length; i++)
369
nodes.add(new WeakReference<Node>(newNodes[i]));
372
// attach listeners to new nodes
373
attachListeners(nodes);
376
/** Removes itself as a listener from given nodes */
377
private void detachListeners(List<Reference<Node>> nodes) {
379
Iterator<Reference<Node>> it = nodes.iterator();
381
while (it.hasNext()) {
382
Node node = it.next().get();
385
node.removeNodeListener(listener);
391
/** Attach itself as a listener to the given nodes */
392
private void attachListeners(List<Reference<Node>> nodes) {
394
Iterator<Reference<Node>> it = nodes.iterator();
396
while (it.hasNext()) {
397
Node node = it.next().get();
400
node.addNodeListener(listener);
406
protected void finalize() {
407
detachListeners(nodes);
410
// end of CookieDelegateAction