--- a/java.api.common/src/org/netbeans/modules/java/api/common/project/BaseActionProvider.java +++ a/java.api.common/src/org/netbeans/modules/java/api/common/project/BaseActionProvider.java @@ -45,6 +45,7 @@ package org.netbeans.modules.java.api.common.project; import java.awt.Dialog; +import java.awt.EventQueue; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -384,6 +385,7 @@ @Override public void invokeAction( final String command, final Lookup context ) throws IllegalArgumentException { + assert EventQueue.isDispatchThread(); if (COMMAND_DELETE.equals(command)) { DefaultProjectOperations.performDefaultDeleteOperation(project); return ; --- a/projectui/nbproject/project.xml +++ a/projectui/nbproject/project.xml @@ -108,7 +108,7 @@ 1 - 1.33 + 1.43 --- a/projectui/src/org/netbeans/modules/project/ui/actions/ActionsUtil.java +++ a/projectui/src/org/netbeans/modules/project/ui/actions/ActionsUtil.java @@ -48,6 +48,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -90,7 +91,7 @@ */ // #74161: do not cache // First find out whether there is a project directly in the Lookup - Set result = new HashSet(); + Set result = new LinkedHashSet(); // XXX or use OpenProjectList.projectByDisplayName? for (Project p : lookup.lookupAll(Project.class)) { result.add(p); } --- a/projectui/src/org/netbeans/modules/project/ui/actions/FileAction.java +++ a/projectui/src/org/netbeans/modules/project/ui/actions/FileAction.java @@ -113,6 +113,7 @@ r[0] = new Runnable() { @Override public void run() { Project[] projects = ActionsUtil.getProjectsFromLookup( context, command ); + // XXX #64991: handle >1 project (tricky since must pass subset of selection to each) if ( projects.length != 1 ) { if (projects.length == 0 && globalProvider(context) != null) { enable[0] = true; --- a/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java +++ a/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java @@ -45,22 +45,20 @@ package org.netbeans.modules.project.ui.actions; import java.awt.Dialog; -import java.awt.Toolkit; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.Arrays; +import java.util.LinkedList; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.project.Project; -import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.ui.OpenProjects; import org.netbeans.modules.project.ui.NoMainProjectWarning; import org.netbeans.modules.project.ui.OpenProjectList; -import org.netbeans.spi.project.ActionProvider; import org.netbeans.spi.project.ui.support.ProjectActionPerformer; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; @@ -72,9 +70,11 @@ import org.openide.util.NbBundle; import org.openide.util.WeakListeners; -/** Invokes command on the main project. - * - * @author Pet Hrebejk +/** + * Similar to {@link ProjectAction} but has a different selection model. + * First uses the main project, if set. + * Else uses the selected projects, if any. + * Finally, if just one project is open, uses that. */ public class MainProjectAction extends LookupSensitiveAction implements PropertyChangeListener { @@ -124,58 +124,27 @@ public @Override void actionPerformed(Lookup context) { - // first try to find main project - Project p = OpenProjectList.getDefault().getMainProject(); - - // then try to find some selected project - if (p == null) { - Project[] projects = ActionsUtil.getProjectsFromLookup(context, command); - if (projects.length == 1) { - p = projects[0]; - } - } - - // then if there is only one project opened in IDE - use it - if (p == null) { - Project[] projects = OpenProjects.getDefault().getOpenProjects(); - if (projects.length == 1) { - p = projects[0]; - } - } + Project mainProject = OpenProjectList.getDefault().getMainProject(); + Project[] projects = selection(mainProject, context); // if no main project or no selected or more than one project opened, // then show warning and allow choose a main project - if (p == null) { + if (projects.length == 0) { // show warning, if cancel then return if (showNoMainProjectWarning (OpenProjectList.getDefault().getOpenProjects (), - getPresenterName(name, OpenProjectList.getDefault().getMainProject(), p))) { + getPresenterName(name, mainProject, projects))) { return ; } - p = OpenProjectList.getDefault().getMainProject(); + projects = new Project[] {OpenProjectList.getDefault().getMainProject()}; } - if ( command != null ) { - ActionProvider ap = p.getLookup().lookup(ActionProvider.class); - if (ap != null) { - if (Arrays.asList(ap.getSupportedActions()).contains(command)) { - ap.invokeAction(command, Lookup.EMPTY); - } else { - // #47160: was a supported command (e.g. on a freeform project) but was then removed. - Toolkit.getDefaultToolkit().beep(); - refreshView(null, false); - } - } - } - else { - performer.perform( p ); + if (command != null && projects.length > 0) { + ProjectAction.runSequentially(new LinkedList(Arrays.asList(projects)), this, command); + } else if (performer != null && projects.length == 1) { + performer.perform(projects[0]); } } - - // Private methods --------------------------------------------------------- - - // Implementation of PropertyChangeListener -------------------------------- - public @Override void propertyChange( PropertyChangeEvent evt ) { if (OpenProjectList.PROPERTY_MAIN_PROJECT.equals(evt.getPropertyName()) || OpenProjectList.PROPERTY_OPEN_PROJECTS.equals(evt.getPropertyName())) { @@ -183,50 +152,51 @@ } } + private Project[] selection(Project mainProject, Lookup context) { + if (mainProject != null) { + return new Project[] {mainProject}; + } + Lookup theContext = context; + if (theContext == null) { + theContext = LastActivatedWindowLookup.INSTANCE; + } + if (theContext != null) { + Project[] projects = ActionsUtil.getProjectsFromLookup(theContext, command); + if (projects.length > 0) { + return projects; + } + } + Project[] projects = OpenProjects.getDefault().getOpenProjects(); + if (projects.length == 1) { + return projects; + } + return new Project[0]; + } + private void refreshView(final Lookup context, boolean immediate) { Runnable r= new Runnable() { public @Override void run() { - Project p = OpenProjectList.getDefault().getMainProject(); - Lookup theContext = context; + Project mainProject = OpenProjectList.getDefault().getMainProject(); + Project[] projects = selection(mainProject, context); - if (p == null) { - if (theContext == null) { - theContext = LastActivatedWindowLookup.INSTANCE; - } - if (theContext != null) { - Project[] projects = ActionsUtil.getProjectsFromLookup(theContext, command); - if (projects.length == 1) { - p = projects[0]; - } - } - } - - if (p == null) { - Project[] projects = OpenProjects.getDefault().getOpenProjects(); - if (projects.length == 1) { - p = projects[0]; - } - } - - Project mainProject = OpenProjectList.getDefault().getMainProject(); - - final String presenterName = getPresenterName(name, mainProject, p); + final String presenterName = getPresenterName(name, mainProject, projects); final boolean enabled; if ( command == null ) { - enabled = performer.enable(p); + enabled = projects.length == 1 && performer.enable(projects[0]); } - else { - if ( p == null ) { - enabled = false; + else if (projects.length == 0) { + enabled = false; + } else { + boolean e = true; + for (Project p : projects) { + if (!ActionsUtil.commandSupported(p, command, Lookup.EMPTY)) { + e = false; + break; + } } - else if ( ActionsUtil.commandSupported ( p, command, Lookup.EMPTY ) ) { - enabled = true; - } - else { - enabled = false; - } + enabled = e; } Mutex.EVENT.writeAccess(new Runnable() { @@ -245,22 +215,14 @@ } } - private String getPresenterName(String name, Project mPrj, Project cPrj) { - String toReturn = ""; - Object[] formatterArgs; - if (mPrj == null) { - if (cPrj == null) { - formatterArgs = new Object[] { 0 }; - } else { - formatterArgs = new Object[] { 1, ProjectUtils.getInformation(cPrj).getDisplayName() }; - } + private String getPresenterName(String name, Project mPrj, Project[] cPrj) { + if (name == null) { + return ""; + } else if (mPrj == null) { + return ActionsUtil.formatProjectSensitiveName(name, cPrj); } else { - formatterArgs = new Object[] { -1 }; + return MessageFormat.format(name, -1); } - if (name != null) { - toReturn = MessageFormat.format(name, formatterArgs); - } - return toReturn; } private boolean showNoMainProjectWarning(Project[] projects, String action) { --- a/projectui/src/org/netbeans/modules/project/ui/actions/ProjectAction.java +++ a/projectui/src/org/netbeans/modules/project/ui/actions/ProjectAction.java @@ -44,11 +44,17 @@ package org.netbeans.modules.project.ui.actions; +import java.awt.Toolkit; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.swing.Action; import javax.swing.Icon; import org.netbeans.api.project.Project; +import org.netbeans.spi.project.ActionProgress; import org.netbeans.spi.project.ActionProvider; import org.netbeans.spi.project.ui.support.ProjectActionPerformer; import org.openide.awt.Actions; @@ -58,6 +64,7 @@ import org.openide.util.Lookup; import org.openide.util.Mutex; import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; /** Action sensitive to current project * @@ -118,26 +125,56 @@ @Override protected void actionPerformed( Lookup context ) { Project[] projects = ActionsUtil.getProjectsFromLookup( context, command ); - - if ( projects.length == 1 ) { - if ( command != null ) { - ActionProvider ap = projects[0].getLookup().lookup(ActionProvider.class); - LogRecord r = new LogRecord(Level.FINE, "PROJECT_ACTION"); // NOI18N - r.setResourceBundle(NbBundle.getBundle(ProjectAction.class)); - r.setParameters(new Object[] { - getClass().getName(), - projects[0].getClass().getName(), - getValue(NAME) - }); - r.setLoggerName(UILOG.getName()); - UILOG.log(r); - ap.invokeAction( command, Lookup.EMPTY ); + if (command != null && projects.length > 0) { + runSequentially(new LinkedList(Arrays.asList(projects)), this, command); + } else if (performer != null && projects.length == 1) { + performer.perform(projects[0]); + } + } + static void runSequentially(final Queue queue, final LookupSensitiveAction a, final String command) { + Project p = queue.remove(); + final ActionProvider ap = p.getLookup().lookup(ActionProvider.class); + if (ap == null) { + return; + } + if (!Arrays.asList(ap.getSupportedActions()).contains(command)) { + // #47160: was a supported command (e.g. on a freeform project) but was then removed. + Toolkit.getDefaultToolkit().beep(); + a.refresh(a.getLookup(), false); + return; + } + LogRecord r = new LogRecord(Level.FINE, "PROJECT_ACTION"); // NOI18N + r.setResourceBundle(NbBundle.getBundle(ProjectAction.class)); + r.setParameters(new Object[] { + a.getClass().getName(), + p.getClass().getName(), + a.getValue(NAME) + }); + r.setLoggerName(UILOG.getName()); + UILOG.log(r); + Mutex.EVENT.writeAccess(new Runnable() { + @Override public void run() { + if (queue.isEmpty()) { + ap.invokeAction(command, Lookup.EMPTY); + } else { + final AtomicBoolean started = new AtomicBoolean(); + ap.invokeAction(command, Lookups.singleton(new ActionProgress() { + @Override protected void started() { + started.set(true); + } + @Override public void finished(boolean success) { + if (success) { // OK, next... + runSequentially(queue, a, command); + } // else build failed, so skip others + } + })); + if (!started.get()) { + // Did not run action for some reason; try others? + runSequentially(queue, a, command); + } + } } - else if ( performer != null ) { - performer.perform( projects[0] ); - } - } - + }); } @Override @@ -147,7 +184,7 @@ Project[] projects = ActionsUtil.getProjectsFromLookup( context, command ); final boolean enable; if ( command != null ) { - enable = projects.length == 1; + enable = projects.length > 0; } else if ( performer != null && projects.length == 1 ) { enable = performer.enable(projects[0]); } else { --- a/projectui/test/unit/src/org/netbeans/modules/project/ui/actions/ProjectActionTest.java +++ a/projectui/test/unit/src/org/netbeans/modules/project/ui/actions/ProjectActionTest.java @@ -47,11 +47,11 @@ import java.util.ArrayList; import java.util.List; import javax.swing.Action; -import javax.swing.KeyStroke; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.junit.MockServices; import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.project.ActionProgress; import org.netbeans.spi.project.ActionProvider; import org.netbeans.spi.project.ui.support.ProjectActionPerformer; import org.openide.filesystems.FileObject; @@ -77,6 +77,7 @@ private DataObject d2_1; private DataObject d2_2; private TestSupport.TestProject project1; + private TestActionProvider tap1; private TestSupport.TestProject project2; @Override protected boolean runInEQ() { @@ -98,7 +99,8 @@ d1_2 = DataObject.find(f1_2); project1 = (TestSupport.TestProject)ProjectManager.getDefault().findProject( p1 ); - project1.setLookup( Lookups.fixed( new Object[] { new TestActionProvider() } ) ); + tap1 = new TestActionProvider(); + project1.setLookup(Lookups.singleton(tap1)); p2 = TestSupport.createTestProject( workDir, "project2" ); f2_1 = p2.createData("f2_1.java"); @@ -125,6 +127,24 @@ assertEnablement(action, false); lookup.change(d1_1, d2_1); assertEnablement(action, false); + TestActionProvider tap2 = new TestActionProvider(); + project2.setLookup(Lookups.singleton(tap2)); + lookup.change(d2_1); + assertEnablement(action, true); + lookup.change(d1_1, d2_1); + assertEnablement(action, true); + action.actionPerformed(null); + assertEquals("[COMMAND]", tap1.invocations.toString()); + assertEquals("[COMMAND]", tap2.invocations.toString()); + tap1.listenerSuccess = true; + tap2.listenerSuccess = true; + action.actionPerformed(null); + assertEquals("[COMMAND, COMMAND]", tap1.invocations.toString()); + assertEquals("[COMMAND, COMMAND]", tap2.invocations.toString()); + tap1.listenerSuccess = false; + action.actionPerformed(null); + assertEquals("[COMMAND, COMMAND, COMMAND]", tap1.invocations.toString()); + assertEquals("[COMMAND, COMMAND]", tap2.invocations.toString()); } public void testProviderEnablement() throws Exception { @@ -177,6 +197,7 @@ private String[] ACTIONS = new String[] { COMMAND }; private List invocations = new ArrayList(); + Boolean listenerSuccess; public String[] getSupportedActions() { return ACTIONS; @@ -186,6 +207,9 @@ if ( COMMAND.equals( command ) ) { invocations.add( command ); + if (listenerSuccess != null) { + ActionProgress.start(context).finished(listenerSuccess); + } } else { throw new IllegalArgumentException();