Issue 102081: make it possible to define single-file actions outside of any project context. diff --git a/projectapi/src/org/netbeans/spi/project/ActionProvider.java b/projectapi/src/org/netbeans/spi/project/ActionProvider.java --- a/projectapi/src/org/netbeans/spi/project/ActionProvider.java +++ b/projectapi/src/org/netbeans/spi/project/ActionProvider.java @@ -46,6 +46,12 @@ /** * Ability for a project to have various actions (e.g. Build) invoked on it. * Should be registered in a project's lookup and will be used by UI infrastructure. + *

+ * Implementations supporting single file commands (command constants ending with + * {@code _SINGLE}) can also be registered in default lookup. If a provider in project + * lookup does not enable the action for a given command on the selected file then + * the first implementation found in default lookup that is enabled will be used. + *

* @see org.netbeans.api.project.Project#getLookup * @see ActionUtils * @see ProjectSensitiveActions.projectCommandAction(...) diff --git a/projectui/src/org/netbeans/modules/project/ui/actions/FileCommandAction.java b/projectui/src/org/netbeans/modules/project/ui/actions/FileCommandAction.java --- a/projectui/src/org/netbeans/modules/project/ui/actions/FileCommandAction.java +++ b/projectui/src/org/netbeans/modules/project/ui/actions/FileCommandAction.java @@ -41,12 +41,15 @@ package org.netbeans.modules.project.ui.actions; +import java.util.Arrays; +import java.util.Collection; import javax.swing.Action; import javax.swing.Icon; import org.netbeans.api.project.Project; import org.netbeans.spi.project.ActionProvider; import org.openide.awt.Actions; import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; @@ -73,8 +76,15 @@ Project[] projects = ActionsUtil.getProjectsFromLookup( context, getCommand() ); if ( projects.length != 1 ) { - setEnabled( false ); // Zero or more than one projects found or command not supported - presenterName = ActionsUtil.formatName( getNamePattern(), 0, "" ); + if (projects.length == 0 && globalProvider(context) != null) { + setEnabled(true); + Collection files = context.lookupAll(DataObject.class); + presenterName = ActionsUtil.formatName(getNamePattern(), files.size(), + files.isEmpty() ? "" : files.iterator().next().getPrimaryFile().getNameExt()); // NOI18N + } else { + setEnabled(false); // Zero or more than one projects found or command not supported + presenterName = ActionsUtil.formatName(getNamePattern(), 0, ""); + } } else { FileObject[] files = ActionsUtil.getFilesFromLookup( context, projects[0] ); @@ -93,8 +103,13 @@ if ( projects.length == 1 ) { ActionProvider ap = projects[0].getLookup().lookup(ActionProvider.class); ap.invokeAction( getCommand(), context ); + return; } + ActionProvider provider = globalProvider(context); + if (provider != null) { + provider.invokeAction(getCommand(), context); + } } @Override @@ -102,4 +117,13 @@ return new FileCommandAction( getCommand(), getNamePattern(), (Icon)getValue( SMALL_ICON ), actionContext ); } + private ActionProvider globalProvider(Lookup context) { + for (ActionProvider ap : Lookup.getDefault().lookupAll(ActionProvider.class)) { + if (Arrays.asList(ap.getSupportedActions()).contains(getCommand()) && ap.isActionEnabled(getCommand(), context)) { + return ap; + } + } + return null; + } + } \ No newline at end of file diff --git a/projectui/test/unit/src/org/netbeans/modules/project/ui/actions/FileCommandActionTest.java b/projectui/test/unit/src/org/netbeans/modules/project/ui/actions/FileCommandActionTest.java --- a/projectui/test/unit/src/org/netbeans/modules/project/ui/actions/FileCommandActionTest.java +++ b/projectui/test/unit/src/org/netbeans/modules/project/ui/actions/FileCommandActionTest.java @@ -45,6 +45,8 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import javax.swing.Action; import javax.swing.Icon; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; @@ -52,12 +54,15 @@ import org.netbeans.junit.NbTestCase; import org.netbeans.modules.project.ui.actions.ProjectActionTest.ActionCreator; import org.netbeans.spi.project.ActionProvider; +import org.netbeans.spi.project.ui.support.FileSensitiveActions; import org.netbeans.spi.project.ui.support.ProjectActionPerformer; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; +import org.openide.util.ContextAwareAction; import org.openide.util.Lookup; import org.openide.util.lookup.Lookups; +import org.openide.util.test.MockLookup; public class FileCommandActionTest extends NbTestCase { @@ -132,6 +137,34 @@ } }, true); } + + public void testGlobalActions() throws Exception { + final AtomicInteger runCount = new AtomicInteger(); + MockLookup.setInstances(new ActionProvider() { + public String[] getSupportedActions() { + return new String[] {"run"}; + } + public boolean isActionEnabled(String command, Lookup context) throws IllegalArgumentException { + DataObject d = context.lookup(DataObject.class); + return d != null && d.getPrimaryFile().getName().equals("foo"); + } + public void invokeAction(String command, Lookup context) throws IllegalArgumentException { + runCount.incrementAndGet(); + } + }); + ContextAwareAction global = (ContextAwareAction) FileSensitiveActions.fileCommandAction("run", "Run {0,choice,0#File|1#\"{1}\"|11 project responds, or file owned by project but project does not respond + } private static class TestActionProvider implements ActionProvider { diff --git a/projectuiapi/apichanges.xml b/projectuiapi/apichanges.xml --- a/projectuiapi/apichanges.xml +++ b/projectuiapi/apichanges.xml @@ -104,6 +104,23 @@ + + + Globally registered ActionProviders + + + + + +

+ ActionProviders may now be registered in global + lookup so as to enable certain file-sensitive actions on certain + selections without the support of any project. +

+
+ + +
OpenProjects.open() supports visual progress diff --git a/projectuiapi/nbproject/project.properties b/projectuiapi/nbproject/project.properties --- a/projectuiapi/nbproject/project.properties +++ b/projectuiapi/nbproject/project.properties @@ -39,7 +39,7 @@ javac.compilerargs=-Xlint -Xlint:-serial javac.source=1.5 -spec.version.base=1.36.0 +spec.version.base=1.37.0 is.autoload=true javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml diff --git a/projectuiapi/src/org/netbeans/spi/project/ui/support/FileSensitiveActions.java b/projectuiapi/src/org/netbeans/spi/project/ui/support/FileSensitiveActions.java --- a/projectuiapi/src/org/netbeans/spi/project/ui/support/FileSensitiveActions.java +++ b/projectuiapi/src/org/netbeans/spi/project/ui/support/FileSensitiveActions.java @@ -60,6 +60,10 @@ * When performed the action will call the given command on the {@link ActionProvider} of * the selected project(s) and pass the proper context to it. Enablement of the * action depends on the behavior of the project's action provider. + *

As mentioned in {@link ActionProvider} Javadoc, the action may also be enabled + * without the participation of any project in case some globally registered {@link ActionProvider} + * can provide an implementation. + * (This since {@code org.netbeans.modules.projectuiapi/1 1.37}.) *

Shortcuts for actions are shared according to command, i.e. actions based on the same command * will have the same shortcut. * @param command the command which should be invoked when the action is