+topComponent.getActionMap ().put ("jumpPrev", new YourPrevAction ()); // NOI18N +topComponent.getActionMap ().put ("jumpNext", new YourNextAction ()); // NOI18N ++ if your component provides items and you want the user to jump + among them using standard next/prev actions. +
String[]
CallableSystemAction
uses its getActionMapKey()
method
+(usually overriden by subclasses) to get a key which is then searched in the
+ActionMap
obtained from the action's context. Other modules can
+register their own action then:
++topComponent.getActionMap ().put (theKey, new YourOwnSwingAction ()); ++Here is the list of special keys: +
"cloneWindow"
- an action to be executed when a top component is to be cloned"closeWindow"
- an action when a view is about to be closedDefaultEditorKit.copyAction
- copy action handlerDefaultEditorKit.cutAction
- cut action handler"delete"
- delete action handlerDefaultEditorKit.pasteAction
- paste action handler"jumpNext"
- when a next element shall be selected"jumpPrev"
- when a previous element shall be selectedLookup
as a representation of context in which are certain action
types ContextAwareAction
s used. Current implementations
-lookup in the context for javax.swing.ActionMap
+lookup in the context for >javax.swing.ActionMap
or org.openide.nodes.Node
or org.openide.Node.Cookie
instances.
null
if there is currently no performer
*/
@@ -121,13 +121,13 @@
/** Updates the enabled state by checking performer and ActionMap
*/
private void updateEnabled () {
- javax.swing.Action action = findGlobalContextAction ();
+ javax.swing.Action action = GlobalManager.getDefault ().findGlobalAction (getActionMapKey (), getSurviveFocusChange ());
if(action != null) {
setEnabled (action.isEnabled());
synchronized (LISTENER) {
ActionDelegateListener l = (ActionDelegateListener)getProperty(LISTENER);
- if (l == null) {
+ if (l == null || l.get () != this) {
l = new ActionDelegateListener (this, action);
putProperty (LISTENER, l);
} else {
@@ -163,7 +163,7 @@
*/
public void actionPerformed(final ActionEvent ev) {
// First try global context action.
- final Action action = findGlobalContextAction ();
+ final Action action = GlobalManager.getDefault ().findGlobalAction (getActionMapKey (), getSurviveFocusChange ());
if (action != null) {
if (action.isEnabled()) {
action.actionPerformed(ev);
@@ -186,19 +186,6 @@
Toolkit.getDefaultToolkit().beep();
}
- /** Finds global context action (the one from activated component).
- * @return action for key of activated component or null
- */
- private javax.swing.Action findGlobalContextAction () {
- ActionMap map = GlobalManager.getDefault ().getActionMap ();
- if (map != null) {
- Object key = getActionMapKey();
- return map.get (key);
- }
-
- return null;
- }
-
/** Perform the action.
* This default implementation calls the assigned action performer if it
* exists, otherwise does nothing.
@@ -295,16 +282,24 @@
it = actions.iterator ();
while (it.hasNext ()) {
CallbackSystemAction a = (CallbackSystemAction)it.next ();
+ if (errLog) {
+ err.log ("updateEnabled: " + a); // NOI18N
+ }
a.updateEnabled ();
}
}
+
+ /** logging */
+ private static final ErrorManager err = ErrorManager.getDefault ().getInstance ("org.openide.util.actions.CallbackSystemAction"); // NOI18N
+ private static final boolean errLog = err.isLoggable (err.INFORMATIONAL);
/** Listener on a global context.
*/
private static final class GlobalManager implements LookupListener {
private static GlobalManager instance;
private Lookup.Result result;
- private Reference actionMap;
+ private Reference actionMap = new WeakReference (null);
+ private final ActionMap survive = new ActionMap ();
public synchronized static GlobalManager getDefault () {
if (instance != null) return instance;
@@ -317,46 +312,112 @@
new Lookup.Template (ActionMap.class)
);
result.addLookupListener(this);
+ resultChanged (null);
}
- public ActionMap getActionMap () {
- ActionMap a = (ActionMap)Utilities.actionsGlobalContext ().lookup (ActionMap.class);
- if (actionMap == null) {
- actionMap = new WeakReference (a);
+ public Action findGlobalAction (Object key, boolean surviveFocusChange) {
+ ActionMap map = (ActionMap)actionMap.get ();
+ Action a = map == null ? null : map.get (key);
+
+ if (surviveFocusChange) {
+ if (a == null) {
+ a = survive.get (key);
+ if (a != null) {
+ a = ((WeakAction)a).getDelegate ();
+ }
+ if (errLog) {
+ err.log ("No action for key: " + key + " using delegate: " + a); // NOI18N
+ }
+ } else {
+ if (errLog) {
+ err.log ("New action for key: " + key + " put: " + a);
+ }
+
+ survive.put (key, new WeakAction (a));
+ }
+ }
+ if (errLog) {
+ err.log ("Action for key: " + key + " is: " + a); // NOI18N
}
return a;
}
/** Change all that do not survive ActionMap change */
public void resultChanged(org.openide.util.LookupEvent ev) {
- if (actionMap == null || actionMap.get () != getActionMap ()) {
- actionMap = null;
- clearActionPerformers ();
+ ActionMap a = (ActionMap)Utilities.actionsGlobalContext ().lookup (ActionMap.class);
+ if (errLog) {
+ err.log ("changed map : " + a); // NOI18N
+ err.log ("previous map: " + actionMap.get ()); // NOI18N
+ }
+ if (a == actionMap.get ()) {
+ return;
+ }
+ actionMap = new WeakReference (a);
+ if (errLog) {
+ err.log ("clearActionPerformers"); // NOI18N
}
+ clearActionPerformers ();
}
} // end of LookupListener
+ /** An action that holds a weak reference to other action.
+ */
+ private static final class WeakAction extends WeakReference implements Action {
+ public WeakAction (Action delegate) {
+ super (delegate);
+ }
+
+ public Action getDelegate () {
+ return (Action)super.get ();
+ }
+
+ public Object getValue (String key) {
+ throw new UnsupportedOperationException ();
+ }
+
+ public void putValue (String key, Object value) {
+ throw new UnsupportedOperationException ();
+ }
+
+ public void actionPerformed (ActionEvent e) {
+ throw new UnsupportedOperationException ();
+ }
+
+ public void removePropertyChangeListener (PropertyChangeListener listener) {
+ throw new UnsupportedOperationException ();
+ }
+
+ public void addPropertyChangeListener (PropertyChangeListener listener) {
+ throw new UnsupportedOperationException ();
+ }
+
+ public void setEnabled (boolean b) {
+ throw new UnsupportedOperationException ();
+ }
+
+ public boolean isEnabled () {
+ throw new UnsupportedOperationException ();
+ }
+ }
+
/** A class that listens on changes in enabled state of an action
* and updates the state of the action according to it.
*/
- private static final class ActionDelegateListener extends Object
+ private static final class ActionDelegateListener extends WeakReference
implements PropertyChangeListener {
- private CallbackSystemAction action;
- private javax.swing.Action delegate;
-
+ private WeakReference delegate;
public ActionDelegateListener (CallbackSystemAction c, javax.swing.Action delegate) {
- this.action = c;
- this.delegate = delegate;
-
-
+ super (c);
+ this.delegate = new WeakReference (delegate);
delegate.addPropertyChangeListener(this);
}
public void clear () {
javax.swing.Action a;
-
- a = delegate;
+
+ WeakReference d = delegate;
+ a = d == null ? null : (javax.swing.Action)d.get ();
if (a == null) return;
delegate = null;
@@ -364,26 +425,31 @@
}
public void attach (javax.swing.Action action) {
- if (delegate == action) {
+ WeakReference d = delegate;
+ if (d != null && d.get () == action) {
return;
}
-
+ Action prev = (Action)d.get ();
// reattaches to different action
- if (this.delegate != null) {
- this.delegate.removePropertyChangeListener(this);
+ if (prev != null) {
+ prev.removePropertyChangeListener(this);
}
- this.delegate = action;
+ this.delegate = new WeakReference (action);
action.addPropertyChangeListener(this);
}
public void propertyChange(java.beans.PropertyChangeEvent evt) {
synchronized (LISTENER) {
- if (delegate == null) return;
+ WeakReference d = delegate;
+ if (d == null || d.get () == null) return;
+ }
+
+ CallbackSystemAction c = (CallbackSystemAction)get ();
+ if (c != null) {
+ c.updateEnabled();
}
-
- action.updateEnabled();
}
}
Index: openide/src/org/openide/windows/DelegateActionMap.java
===================================================================
RCS file: /cvs/openide/src/org/openide/windows/DelegateActionMap.java,v
retrieving revision 1.6
diff -u -r1.6 DelegateActionMap.java
--- openide/src/org/openide/windows/DelegateActionMap.java 22 Jun 2004 14:23:25 -0000 1.6
+++ openide/src/org/openide/windows/DelegateActionMap.java 22 Mar 2005 08:37:38 -0000
@@ -127,4 +127,7 @@
return delegate == null ? null : delegate.getParent ();
}
+ public String toString () {
+ return super.toString () + " for " + this.component;
+ }
}
Index: openide/src/org/openide/windows/TopComponent.java
===================================================================
RCS file: /cvs/openide/src/org/openide/windows/TopComponent.java,v
retrieving revision 1.138
diff -u -r1.138 TopComponent.java
--- openide/src/org/openide/windows/TopComponent.java 8 Feb 2005 15:06:48 -0000 1.138
+++ openide/src/org/openide/windows/TopComponent.java 22 Mar 2005 08:37:49 -0000
@@ -557,6 +557,7 @@
* unless it is in active mode already. */
public void requestVisible () {
WindowManager.getDefault().topComponentRequestVisible(this);
+ org.netbeans.modules.openide.windows.GlobalActionContextImpl.blickActionMap (getActionMap ());
}
/**
Index: openide/test/unit/src/org/openide/util/actions/CallbackSystemActionTest.java
===================================================================
RCS file: /cvs/openide/test/unit/src/org/openide/util/actions/CallbackSystemActionTest.java,v
retrieving revision 1.11
diff -u -r1.11 CallbackSystemActionTest.java
--- openide/test/unit/src/org/openide/util/actions/CallbackSystemActionTest.java 16 Mar 2005 10:56:47 -0000 1.11
+++ openide/test/unit/src/org/openide/util/actions/CallbackSystemActionTest.java 22 Mar 2005 08:37:49 -0000
@@ -13,6 +13,7 @@
package org.openide.util.actions;
+import java.lang.ref.WeakReference;
import javax.swing.ActionMap;
import org.netbeans.junit.*;
import junit.textui.TestRunner;
@@ -34,14 +35,74 @@
super(name);
}
- public static void main(String[] args) {
- TestRunner.run(new NbTestSuite(CallbackSystemActionTest.class));
- // May have used AWT thread.
- System.exit(0);
+
+ protected void setUp () throws Exception {
+ super.setUp();
}
protected boolean runInEQ () {
return true;
+ }
+
+ public void testSurviveFocusChangeInTheNewWay () throws Exception {
+ doSurviveFocusChangeInTheNewWay (false);
+ }
+
+ public void testSurviveFocusChangeInTheNewWayEvenActionIsGCed () throws Exception {
+ doSurviveFocusChangeInTheNewWay (true);
+ }
+
+ private void doSurviveFocusChangeInTheNewWay (boolean doGC) throws Exception {
+ class MyAction extends javax.swing.AbstractAction {
+ public int cntEnabled;
+ public int cntPerformed;
+
+ public boolean isEnabled () {
+ cntEnabled++;
+ return true;
+ }
+
+ public void actionPerformed (java.awt.event.ActionEvent ev) {
+ cntPerformed++;
+ }
+ }
+ MyAction myAction = new MyAction ();
+
+ TopComponent other = new TopComponent ();
+ TopComponent tc = new TopComponent ();
+ SurviveFocusChgCallbackAction a = (SurviveFocusChgCallbackAction)SurviveFocusChgCallbackAction.get (SurviveFocusChgCallbackAction.class);
+ tc.getActionMap().put (a.getActionMapKey (), myAction);
+
+ ActionsInfraHid.UT.setActivated(other);
+ try {
+ assertFalse ("Disabled on other component", a.isEnabled ());
+ ActionsInfraHid.UT.setActivated (tc);
+ assertTrue ("MyAction is enabled", a.isEnabled ());
+ assertEquals ("isEnabled called once", 1, myAction.cntEnabled);
+
+ if (doGC) {
+ java.lang.ref.WeakReference ref = new java.lang.ref.WeakReference (a);
+ a = null;
+ assertGC ("Action can disappear", ref);
+ a = (SurviveFocusChgCallbackAction)SurviveFocusChgCallbackAction.get (SurviveFocusChgCallbackAction.class);
+ }
+
+ ActionsInfraHid.UT.setActivated (other);
+ assertTrue ("Still enabled", a.isEnabled ());
+ assertEquals ("isEnabled called still only once (now it is called twice)", 2, myAction.cntEnabled);
+ } finally {
+ ActionsInfraHid.UT.setActivated (null);
+ }
+
+ WeakReference ref = new WeakReference (a);
+ WeakReference ref2 = new WeakReference (myAction);
+ WeakReference ref3 = new WeakReference (tc);
+ a = null;
+ myAction = null;
+ tc = null;
+ assertGC ("We are able to clear global action", ref);
+ assertGC ("Even our action", ref2);
+ assertGC ("Even our component", ref3);
}
/** Make sure that the performer system works and controls enablement.
Index: openide/test/unit/src/org/openide/windows/GlobalContextImplTest.java
===================================================================
RCS file: /cvs/openide/test/unit/src/org/openide/windows/GlobalContextImplTest.java,v
retrieving revision 1.6
diff -u -r1.6 GlobalContextImplTest.java
--- openide/test/unit/src/org/openide/windows/GlobalContextImplTest.java 16 Mar 2005 19:35:17 -0000 1.6
+++ openide/test/unit/src/org/openide/windows/GlobalContextImplTest.java 22 Mar 2005 08:37:49 -0000
@@ -24,6 +24,7 @@
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
+import org.openide.util.LookupEvent;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
@@ -106,6 +107,54 @@
assertEquals ("No change", 3, cnt);
assertEquals ("No items in lookup", 0, result.allItems ().size ());
+ assertActionMap ();
+ }
+
+ public void testRequestVisibleBlinksTheActionMapForAWhile () throws Exception {
+ final org.openide.nodes.Node n = new org.openide.nodes.AbstractNode (org.openide.nodes.Children.LEAF);
+ tc.setActivatedNodes(new Node[] { n });
+
+ assertActionMap ();
+ final Lookup.Result res = lookup.lookup (new Lookup.Template (ActionMap.class));
+ assertEquals ("One action map", 1, res.allItems ().size ());
+
+ class L implements org.openide.util.LookupListener {
+ ArrayList maps = new ArrayList ();
+
+ public void resultChanged (org.openide.util.LookupEvent ev) {
+ assertEquals ("Still only one", 1, res.allItems ().size ());
+ Lookup.Item i = (Lookup.Item)res.allItems ().iterator ().next ();
+ assertNotNull (i);
+
+ maps.add (i.getInstance ());
+
+ assertNode ();
+ }
+
+ public void assertNode () {
+ assertEquals ("The node is available", n, lookup.lookup (Node.class));
+ }
+ }
+ L myListener = new L ();
+ myListener.assertNode ();
+
+ res.addLookupListener (myListener);
+
+ TopComponent my = new TopComponent ();
+ my.requestVisible ();
+
+ if (myListener.maps.size () != 2) {
+ fail ("Expected two changes in the ActionMaps: " + myListener.maps);
+ }
+
+ myListener.assertNode ();
+
+ ActionMap m1 = (ActionMap)myListener.maps.get (0);
+ ActionMap m2 = (ActionMap)myListener.maps.get (1);
+
+ assertNull ("Our action is not in first map", m1.get (this));
+ assertEquals ("Our action is in second map", sampleAction, m2.get (this));
+
assertActionMap ();
}
Index: openide/windows/src/org/netbeans/modules/openide/windows/GlobalActionContextImpl.java
===================================================================
RCS file: /cvs/openide/windows/src/org/netbeans/modules/openide/windows/GlobalActionContextImpl.java,v
retrieving revision 1.4
diff -u -r1.4 GlobalActionContextImpl.java
--- openide/windows/src/org/netbeans/modules/openide/windows/GlobalActionContextImpl.java 12 Aug 2003 09:38:20 -0000 1.4
+++ openide/windows/src/org/netbeans/modules/openide/windows/GlobalActionContextImpl.java 22 Mar 2005 08:37:49 -0000
@@ -15,6 +15,8 @@
import org.openide.util.Lookup;
import org.openide.util.ContextGlobalProvider;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ProxyLookup;
import org.openide.windows.TopComponent;
/** An interface that can be registered in a lookup by subsystems
@@ -35,6 +37,33 @@
this.registry = r;
}
+ /** the lookup to temporarily use */
+ private static volatile Lookup temporary;
+ /** Temporarily provides different action map in the lookup.
+ */
+ public static void blickActionMap (javax.swing.ActionMap map) {
+ Object obj = Lookup.getDefault ().lookup (ContextGlobalProvider.class);
+ if (obj instanceof GlobalActionContextImpl) {
+ GlobalActionContextImpl g = (GlobalActionContextImpl)obj;
+
+ Lookup[] arr = {
+ Lookups.singleton (map),
+ Lookups.exclude (g.getLookup (), new Class[] { javax.swing.ActionMap.class }),
+ };
+
+ Lookup prev = temporary;
+ try {
+ temporary = new ProxyLookup (arr);
+ Object q = org.openide.util.Utilities.actionsGlobalContext ().lookup (javax.swing.ActionMap.class);
+ assert q == map : "We really get map from the lookup. Map: " + map + " returned: " + q; // NOI18N
+ } finally {
+ temporary = prev;
+ // fire the changes about return of the values back
+ org.openide.util.Utilities.actionsGlobalContext ().lookup (javax.swing.ActionMap.class);
+ }
+ }
+ }
+
/** Let's create the proxy listener that delegates to currently
* selected top component.
*/
@@ -45,6 +74,11 @@
/** The current component lookup */
public Lookup getLookup() {
+ Lookup l = temporary;
+ if (l != null) {
+ return l;
+ }
+
TopComponent tc = registry.getActivated();
return tc == null ? Lookup.EMPTY : tc.getLookup();
}
Index: projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml
===================================================================
RCS file: /cvs/projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml,v
retrieving revision 1.47
diff -u -r1.47 layer.xml
--- projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml 19 Jan 2005 13:33:54 -0000 1.47
+++ projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml 22 Mar 2005 08:37:58 -0000
@@ -354,7 +354,7 @@
- No
+ jumpNext
and jumpPrev
action
+ handlers in its ActionMap
.
+
+ jumpNext
and jumpPrev
action
+ handlers in its ActionMap
.
+