Index: ant/project/apichanges.xml diff -u ant/project/apichanges.xml:1.12 ant/project/apichanges.xml:1.11.28.2 --- ant/project/apichanges.xml:1.12 Fri Jun 30 11:05:46 2006 +++ ant/project/apichanges.xml Mon Aug 21 13:33:13 2006 @@ -82,6 +82,25 @@ + + + Added utilities for constructing richer property evaluators + + + + + +

+ Added a new method and nested class to PropertyUtils to + make it easier to write a customizer version of + AntProjectHelper.getStandardPropertyEvaluator(), + among other things. +

+
+ + +
+ Semantic changes in the ReferenceHelper behavior Index: ant/project/manifest.mf diff -u ant/project/manifest.mf:1.17 ant/project/manifest.mf:1.17.12.1 --- ant/project/manifest.mf:1.17 Mon Dec 12 07:37:33 2005 +++ ant/project/manifest.mf Thu Jun 15 13:38:20 2006 @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.project.ant/1 -OpenIDE-Module-Specification-Version: 1.13 +OpenIDE-Module-Specification-Version: 1.14 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/project/ant/Bundle.properties OpenIDE-Module-Install: org/netbeans/modules/project/ant/AntProjectModule.class Index: ant/project/src/org/netbeans/spi/project/support/ant/ProjectProperties.java diff -u ant/project/src/org/netbeans/spi/project/support/ant/ProjectProperties.java:1.16 ant/project/src/org/netbeans/spi/project/support/ant/ProjectProperties.java:1.14.22.2 --- ant/project/src/org/netbeans/spi/project/support/ant/ProjectProperties.java:1.16 Thu Aug 3 12:21:07 2006 +++ ant/project/src/org/netbeans/spi/project/support/ant/ProjectProperties.java Mon Aug 21 13:33:21 2006 @@ -19,8 +19,6 @@ package org.netbeans.spi.project.support.ant; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -400,7 +398,8 @@ PropertyEvaluator findUserPropertiesFile = PropertyUtils.sequentialPropertyEvaluator( getStockPropertyPreprovider(), getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH)); - PropertyProvider globalProperties = new UserPropertiesProvider(findUserPropertiesFile); + PropertyProvider globalProperties = PropertyUtils.userPropertiesProvider(findUserPropertiesFile, + "user.properties.file", FileUtil.toFile(helper.getProjectDirectory())); // NOI18N standardPropertyEvaluator = PropertyUtils.sequentialPropertyEvaluator( getStockPropertyPreprovider(), getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH), @@ -408,35 +407,6 @@ getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH)); } return standardPropertyEvaluator; - } - private PropertyProvider computeDelegate(PropertyEvaluator findUserPropertiesFile) { - String userPropertiesFile = findUserPropertiesFile.getProperty("user.properties.file"); // NOI18N - if (userPropertiesFile != null) { - // Have some defined global properties file, so read it and listen to changes in it. - File f = helper.resolveFile(userPropertiesFile); - if (f.equals(PropertyUtils.userBuildProperties())) { - // Just to share the cache. - return PropertyUtils.globalPropertyProvider(); - } else { - return PropertyUtils.propertiesFilePropertyProvider(f); - } - } else { - // Use the in-IDE default. - return PropertyUtils.globalPropertyProvider(); - } - } - private final class UserPropertiesProvider extends PropertyUtils.DelegatingPropertyProvider implements PropertyChangeListener { - private final PropertyEvaluator findUserPropertiesFile; - public UserPropertiesProvider(PropertyEvaluator findUserPropertiesFile) { - super(computeDelegate(findUserPropertiesFile)); - this.findUserPropertiesFile = findUserPropertiesFile; - findUserPropertiesFile.addPropertyChangeListener(this); - } - public void propertyChange(PropertyChangeEvent ev) { - if ("user.properties.file".equals(ev.getPropertyName())) { // NOI18N - setDelegate(computeDelegate(findUserPropertiesFile)); - } - } } } Index: ant/project/src/org/netbeans/spi/project/support/ant/PropertyUtils.java diff -u ant/project/src/org/netbeans/spi/project/support/ant/PropertyUtils.java:1.35 ant/project/src/org/netbeans/spi/project/support/ant/PropertyUtils.java:1.33.12.3 --- ant/project/src/org/netbeans/spi/project/support/ant/PropertyUtils.java:1.35 Thu Aug 3 12:21:07 2006 +++ ant/project/src/org/netbeans/spi/project/support/ant/PropertyUtils.java Mon Aug 21 13:33:22 2006 @@ -710,6 +710,55 @@ public static PropertyEvaluator sequentialPropertyEvaluator(PropertyProvider preprovider, PropertyProvider... providers) { return new SequentialPropertyEvaluator(preprovider, providers); } + + /** + * Creates a property provider similar to {@link #globalPropertyProvider} + * but which can use a different global properties file. + * If a specific file is pointed to, that is loaded; otherwise behaves like {@link #globalPropertyProvider}. + * Permits behavior similar to command-line Ant where not erroneous, but using the IDE's + * default global properties for projects which do not yet have this property registered. + * @param findUserPropertiesFile an evaluator in which to look up propertyName + * @param propertyName a property pointing to the global properties file (typically "user.properties.file") + * @param basedir a base directory to use when resolving the path to the global properties file, if relative + * @return a provider of global properties + * @since org.netbeans.modules.project.ant/1 1.14 + */ + public static PropertyProvider userPropertiesProvider(PropertyEvaluator findUserPropertiesFile, String propertyName, File basedir) { + return new UserPropertiesProvider(findUserPropertiesFile, propertyName, basedir); + } + private static final class UserPropertiesProvider extends DelegatingPropertyProvider implements PropertyChangeListener { + private final PropertyEvaluator findUserPropertiesFile; + private final String propertyName; + private final File basedir; + public UserPropertiesProvider(PropertyEvaluator findUserPropertiesFile, String propertyName, File basedir) { + super(computeDelegate(findUserPropertiesFile, propertyName, basedir)); + this.findUserPropertiesFile = findUserPropertiesFile; + this.propertyName = propertyName; + this.basedir = basedir; + findUserPropertiesFile.addPropertyChangeListener(this); + } + public void propertyChange(PropertyChangeEvent ev) { + if (propertyName.equals(ev.getPropertyName())) { + setDelegate(computeDelegate(findUserPropertiesFile, propertyName, basedir)); + } + } + private static PropertyProvider computeDelegate(PropertyEvaluator findUserPropertiesFile, String propertyName, File basedir) { + String userPropertiesFile = findUserPropertiesFile.getProperty(propertyName); + if (userPropertiesFile != null) { + // Have some defined global properties file, so read it and listen to changes in it. + File f = PropertyUtils.resolveFile(basedir, userPropertiesFile); + if (f.equals(PropertyUtils.userBuildProperties())) { + // Just to share the cache. + return PropertyUtils.globalPropertyProvider(); + } else { + return PropertyUtils.propertiesFilePropertyProvider(f); + } + } else { + // Use the in-IDE default. + return PropertyUtils.globalPropertyProvider(); + } + } + } private static final class SequentialPropertyEvaluator implements PropertyEvaluator, ChangeListener { @@ -839,19 +888,34 @@ /** * Property provider that delegates to another source. - * Currently attaches a strong listener to it. + * Useful, for example, when conditionally loading from one or another properties file. + * @since org.netbeans.modules.project.ant/1 1.14 */ - static abstract class DelegatingPropertyProvider implements PropertyProvider, ChangeListener { + public static abstract class DelegatingPropertyProvider implements PropertyProvider { private PropertyProvider delegate; private final List listeners = new ArrayList(); + private final ChangeListener strongListener = new ChangeListener() { + public void stateChanged(ChangeEvent e) { + //System.err.println("DPP: change from current provider " + delegate); + fireChange(); + } + }; private ChangeListener weakListener = null; // #50572: must be weak - + + /** + * Initialize the proxy. + * @param delegate the initial delegate to use + */ protected DelegatingPropertyProvider(PropertyProvider delegate) { assert delegate != null; setDelegate(delegate); } + /** + * Change the current delegate (firing changes as well). + * @param delegate the initial delegate to use + */ protected final void setDelegate(PropertyProvider delegate) { if (delegate == this.delegate) { return; @@ -861,7 +925,7 @@ this.delegate.removeChangeListener(weakListener); } this.delegate = delegate; - weakListener = WeakListeners.change(this, delegate); + weakListener = WeakListeners.change(strongListener, delegate); delegate.addChangeListener(weakListener); fireChange(); } @@ -893,11 +957,6 @@ } } - public final void stateChanged(ChangeEvent changeEvent) { - //System.err.println("DPP: change from current provider " + delegate); - fireChange(); - } - } } Index: ant/project/test/unit/src/org/netbeans/spi/project/support/ant/PropertyUtilsTest.java diff -u ant/project/test/unit/src/org/netbeans/spi/project/support/ant/PropertyUtilsTest.java:1.21 ant/project/test/unit/src/org/netbeans/spi/project/support/ant/PropertyUtilsTest.java:1.19.42.2 --- ant/project/test/unit/src/org/netbeans/spi/project/support/ant/PropertyUtilsTest.java:1.21 Thu Aug 3 12:21:08 2006 +++ ant/project/test/unit/src/org/netbeans/spi/project/support/ant/PropertyUtilsTest.java Mon Aug 21 13:33:26 2006 @@ -563,7 +563,6 @@ } public void testDelegatingPropertyProvider() throws Exception { - // Used only by ProjectProperties, not publically, but still worth testing. TestMutablePropertyProvider mpp = new TestMutablePropertyProvider(new HashMap()); DPP dpp = new DPP(mpp); AntBasedTestUtil.TestCL l = new AntBasedTestUtil.TestCL(); Index: java/j2seproject/nbproject/project.properties diff -u java/j2seproject/nbproject/project.properties:1.29 java/j2seproject/nbproject/project.properties:1.26.4.2 --- java/j2seproject/nbproject/project.properties:1.29 Tue Aug 15 09:10:51 2006 +++ java/j2seproject/nbproject/project.properties Mon Aug 21 13:33:29 2006 @@ -15,6 +15,8 @@ # Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun # Microsystems, Inc. All Rights Reserved. +javac.compilerargs=-Xlint +javac.source=1.5 spec.version.base=1.7.0 javadoc.arch=${basedir}/arch.xml Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/Bundle.properties diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/Bundle.properties:1.28 java/j2seproject/src/org/netbeans/modules/java/j2seproject/Bundle.properties:1.27.2.2 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/Bundle.properties:1.28 Fri Jun 30 13:07:38 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/Bundle.properties Mon Aug 21 13:33:30 2006 @@ -75,3 +75,5 @@ MSG_OldProjectMetadata=The project is not of the current version. CopyLibs=CopyLibs Task + +J2SEConfigurationProvider.default.label= Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java:1.50 java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java:1.49.18.6 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java:1.50 Fri Jun 30 13:07:38 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEActionProvider.java Tue Aug 29 11:59:26 2006 @@ -42,6 +42,7 @@ import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.modules.java.j2seproject.applet.AppletSupport; +import org.netbeans.modules.java.j2seproject.ui.customizer.J2SEProjectProperties; import org.netbeans.modules.java.j2seproject.ui.customizer.MainClassChooser; import org.netbeans.modules.java.j2seproject.ui.customizer.MainClassWarning; import org.netbeans.modules.javacore.JMManager; @@ -277,23 +278,42 @@ p.setProperty("fix.includes", path); // NOI18N } else if (command.equals (COMMAND_RUN) || command.equals(COMMAND_DEBUG) || command.equals(COMMAND_DEBUG_STEP_INTO)) { - EditableProperties ep = updateHelper.getProperties (AntProjectHelper.PROJECT_PROPERTIES_PATH); + String config = project.evaluator().getProperty(J2SEConfigurationProvider.PROP_CONFIG); + String path; + if (config == null || config.length() == 0) { + path = AntProjectHelper.PROJECT_PROPERTIES_PATH; + } else { + // Set main class for a particular config only. + path = "nbproject/configs/" + config + ".properties"; // NOI18N + } + EditableProperties ep = updateHelper.getProperties(path); // check project's main class - String mainClass = (String)ep.get ("main.class"); // NOI18N - int result = isSetMainClass (project.getSourceRoots().getRoots(), mainClass); - if (result != 0) { + // Check whether main class is defined in this config. Note that we use the evaluator, + // not ep.getProperty(MAIN_CLASS), since it is permissible for the default pseudoconfig + // to define a main class - in this case an active config need not override it. + String mainClass = project.evaluator().getProperty(J2SEProjectProperties.MAIN_CLASS); + MainClassStatus result = isSetMainClass (project.getSourceRoots().getRoots(), mainClass); + if (context.lookup(J2SEConfigurationProvider.Config.class) != null) { + // If a specific config was selected, just skip this check for now. + // XXX would ideally check that that config in fact had a main class. + // But then evaluator.getProperty(MAIN_CLASS) would be inaccurate. + // Solvable but punt on it for now. + result = MainClassStatus.SET_AND_VALID; + } + if (result != MainClassStatus.SET_AND_VALID) { do { // show warning, if cancel then return if (showMainClassWarning (mainClass, ProjectUtils.getInformation(project).getDisplayName(), ep,result)) { return null; } - mainClass = (String)ep.get ("main.class"); // NOI18N + // No longer use the evaluator: have not called putProperties yet so it would not work. + mainClass = ep.get(J2SEProjectProperties.MAIN_CLASS); result=isSetMainClass (project.getSourceRoots().getRoots(), mainClass); - } while (result != 0); + } while (result != MainClassStatus.SET_AND_VALID); try { if (updateHelper.requestSave()) { - updateHelper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH,ep); + updateHelper.putProperties(path, ep); ProjectManager.getDefault().saveProject(project); } else { @@ -389,6 +409,17 @@ throw new IllegalArgumentException(command); } } + J2SEConfigurationProvider.Config c = context.lookup(J2SEConfigurationProvider.Config.class); + if (c != null) { + String config; + if (c.name != null) { + config = c.name; + } else { + // Invalid but overrides any valid setting in config.properties. + config = ""; + } + p.setProperty(J2SEConfigurationProvider.PROP_CONFIG, config); + } return targetNames; } @@ -551,31 +582,34 @@ return srcDir; } + private static enum MainClassStatus { + SET_AND_VALID, + SET_BUT_INVALID, + UNSET + } /** * Tests if the main class is set * @param sourcesRoots source roots * @param mainClass main class name - * @return 0 if the main class is set and is valid - * -1 if the main class is not set - * -2 if the main class is set but is not valid + * @return status code */ - private int isSetMainClass (FileObject[] sourcesRoots, String mainClass) { + private MainClassStatus isSetMainClass(FileObject[] sourcesRoots, String mainClass) { // support for unit testing if (MainClassChooser.unitTestingSupport_hasMainMethodResult != null) { - return MainClassChooser.unitTestingSupport_hasMainMethodResult.booleanValue () ? 0 : -2; + return MainClassChooser.unitTestingSupport_hasMainMethodResult ? MainClassStatus.SET_AND_VALID : MainClassStatus.SET_BUT_INVALID; } if (mainClass == null || mainClass.length () == 0) { - return -1; + return MainClassStatus.UNSET; } ClassPath classPath = ClassPath.getClassPath (sourcesRoots[0], ClassPath.EXECUTE); //Single compilation unit if (J2SEProjectUtil.isMainClass (mainClass, classPath)) { - return 0; + return MainClassStatus.SET_AND_VALID; } - return -2; + return MainClassStatus.SET_BUT_INVALID; } /** Checks if given file object contains the main method. @@ -590,17 +624,13 @@ } try { DataObject classDO = DataObject.find (classFO); - Object obj = classDO.getCookie (SourceCookie.class); - if (obj == null || !(obj instanceof SourceCookie)) { + SourceCookie cookie = classDO.getCookie(SourceCookie.class); + if (cookie == null) { return false; } - SourceCookie cookie = (SourceCookie) obj; // check the main class - SourceElement source = cookie.getSource (); - ClassElement[] classes = source.getClasses(); - boolean hasMain = false; - for (int i = 0; i < classes.length; i++) { - if (classes[i].hasMainMethod()) { + for (ClassElement clazz : cookie.getSource().getClasses()) { + if (clazz.hasMainMethod()) { return true; } } @@ -615,11 +645,11 @@ * Asks user for name of main class * @param mainClass current main class * @param projectName the name of project - * @param ep EditableProperties - * @param messgeType type of dialog -1 when the main class is not set, -2 when the main class in not valid + * @param ep project.properties to possibly edit + * @param messgeType type of dialog * @return true if user selected main class */ - private boolean showMainClassWarning (String mainClass, String projectName, EditableProperties ep, int messageType) { + private boolean showMainClassWarning(String mainClass, String projectName, EditableProperties ep, MainClassStatus messageType) { boolean canceled; final JButton okButton = new JButton (NbBundle.getMessage (MainClassWarning.class, "LBL_MainClassWarning_ChooseMainClass_OK")); // NOI18N okButton.getAccessibleContext().setAccessibleDescription (NbBundle.getMessage (MainClassWarning.class, "AD_MainClassWarning_ChooseMainClass_OK")); @@ -627,12 +657,12 @@ // main class goes wrong => warning String message; switch (messageType) { - case -1: + case UNSET: message = MessageFormat.format (NbBundle.getMessage(MainClassWarning.class,"LBL_MainClassNotFound"), new Object[] { projectName }); break; - case -2: + case SET_BUT_INVALID: message = MessageFormat.format (NbBundle.getMessage(MainClassWarning.class,"LBL_MainClassWrong"), new Object[] { mainClass, projectName @@ -670,7 +700,7 @@ } else { mainClass = panel.getSelectedMainClass (); canceled = false; - ep.put ("main.class", mainClass == null ? "" : mainClass); // NOI18N + ep.put(J2SEProjectProperties.MAIN_CLASS, mainClass == null ? "" : mainClass); } dlg.dispose(); Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEConfigurationProvider.java diff -u /dev/null java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEConfigurationProvider.java:1.1.2.11 --- /dev/null Thu Aug 31 13:03:57 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEConfigurationProvider.java Tue Aug 29 11:47:20 2006 @@ -0,0 +1,233 @@ +/* + * 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-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.modules.java.j2seproject; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.IOException; +import java.io.InputStream; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.modules.java.j2seproject.ui.customizer.CustomizerProviderImpl; +import org.netbeans.modules.java.j2seproject.ui.customizer.J2SECompositePanelProvider; +import org.netbeans.spi.project.ActionProvider; +import org.netbeans.spi.project.ProjectConfiguration; +import org.netbeans.spi.project.ProjectConfigurationProvider; +import org.netbeans.spi.project.support.ant.EditableProperties; +import org.openide.filesystems.FileChangeAdapter; +import org.openide.filesystems.FileChangeListener; +import org.openide.filesystems.FileEvent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileRenameEvent; +import org.openide.filesystems.FileUtil; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; + +/** + * Manages configurations for a Java SE project. + * @author Jesse Glick + */ +final class J2SEConfigurationProvider implements ProjectConfigurationProvider { + + /** + * Ant property name for active config. + */ + public static final String PROP_CONFIG = "config"; // NOI18N + /** + * Ant property file which specified active config. + */ + public static final String CONFIG_PROPS_PATH = "nbproject/private/config.properties"; // NOI18N + + public static final class Config implements ProjectConfiguration { + /** file basename, or null for default config */ + public final String name; + private final String displayName; + public Config(String name, String displayName) { + this.name = name; + this.displayName = displayName; + } + public String getDisplayName() { + return displayName; + } + public int hashCode() { + return name != null ? name.hashCode() : 0; + } + public boolean equals(Object o) { + return (o instanceof Config) && Utilities.compareObjects(name, ((Config) o).name); + } + public String toString() { + return "J2SEConfigurationProvider.Config[" + name + "," + displayName + "]"; // NOI18N + } + } + + private static final Config DEFAULT = new Config(null, + NbBundle.getMessage(J2SEConfigurationProvider.class, "J2SEConfigurationProvider.default.label")); + + private final J2SEProject p; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private final FileChangeListener fcl = new FileChangeAdapter() { + public void fileFolderCreated(FileEvent fe) { + update(); + } + public void fileDataCreated(FileEvent fe) { + update(); + } + public void fileDeleted(FileEvent fe) { + update(); + } + public void fileRenamed(FileRenameEvent fe) { + update(); + } + private void update() { + Set oldConfigs = configs != null ? configs.keySet() : Collections.emptySet(); + configDir = p.getProjectDirectory().getFileObject("nbproject/configs"); // NOI18N + if (configDir != null) { + configDir.removeFileChangeListener(fclWeak); + configDir.addFileChangeListener(fclWeak); + } + calculateConfigs(); + Set newConfigs = configs.keySet(); + if (!oldConfigs.equals(newConfigs)) { + pcs.firePropertyChange(ProjectConfigurationProvider.PROP_CONFIGURATIONS, null, null); + // XXX also fire PROP_ACTIVE_CONFIGURATION? + } + } + }; + private final FileChangeListener fclWeak; + private FileObject configDir; + private Map configs; + + public J2SEConfigurationProvider(J2SEProject p) { + this.p = p; + fclWeak = FileUtil.weakFileChangeListener(fcl, null); + FileObject nbp = p.getProjectDirectory().getFileObject("nbproject"); // NOI18N + if (nbp != null) { + nbp.addFileChangeListener(fclWeak); + configDir = nbp.getFileObject("configs"); // NOI18N + if (configDir != null) { + configDir.addFileChangeListener(fclWeak); + } + } + p.evaluator().addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (PROP_CONFIG.equals(evt.getPropertyName())) { + pcs.firePropertyChange(PROP_CONFIGURATION_ACTIVE, null, null); + } + } + }); + } + + private void calculateConfigs() { + configs = new HashMap(); + if (configDir != null) { + for (FileObject kid : configDir.getChildren()) { + if (!kid.hasExt("properties")) { + continue; + } + try { + InputStream is = kid.getInputStream(); + try { + Properties p = new Properties(); + p.load(is); + String name = kid.getName(); + String label = p.getProperty("$label"); // NOI18N + configs.put(name, new Config(name, label != null ? label : name)); + } finally { + is.close(); + } + } catch (IOException x) { + Logger.getLogger(J2SEConfigurationProvider.class.getName()).log(Level.INFO, null, x); + } + } + } + } + + public Collection getConfigurations() { + calculateConfigs(); + List l = new ArrayList(); + l.addAll(configs.values()); + Collections.sort(l, new Comparator() { + Collator c = Collator.getInstance(); + public int compare(Config c1, Config c2) { + return c.compare(c1.getDisplayName(), c2.getDisplayName()); + } + }); + l.add(0, DEFAULT); + return l; + } + + public Config getActiveConfiguration() { + calculateConfigs(); + String config = p.evaluator().getProperty(PROP_CONFIG); + if (config != null && configs.containsKey(config)) { + return configs.get(config); + } else { + return DEFAULT; + } + } + + public void setActiveConfiguration(Config c) throws IllegalArgumentException, IOException { + if (c != DEFAULT && !configs.values().contains(c)) { + throw new IllegalArgumentException(); + } + final String n = c.name; + EditableProperties ep = p.getUpdateHelper().getProperties(CONFIG_PROPS_PATH); + if (Utilities.compareObjects(n, ep.getProperty(PROP_CONFIG))) { + return; + } + if (n != null) { + ep.setProperty(PROP_CONFIG, n); + } else { + ep.remove(PROP_CONFIG); + } + p.getUpdateHelper().putProperties(CONFIG_PROPS_PATH, ep); + pcs.firePropertyChange(ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE, null, null); + ProjectManager.getDefault().saveProject(p); + assert p.getProjectDirectory().getFileObject(CONFIG_PROPS_PATH) != null; + } + + public boolean hasCustomizer() { + return true; + } + + public void customize() { + p.getLookup().lookup(CustomizerProviderImpl.class).showCustomizer(J2SECompositePanelProvider.RUN); + } + + public boolean configurationsAffectAction(String command) { + return command.equals(ActionProvider.COMMAND_RUN) || + command.equals(ActionProvider.COMMAND_DEBUG); + } + + public void addPropertyChangeListener(PropertyChangeListener lst) { + pcs.addPropertyChangeListener(lst); + } + + public void removePropertyChangeListener(PropertyChangeListener lst) { + pcs.removePropertyChangeListener(lst); + } + +} Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java:1.62 java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java:1.61.4.5 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java:1.62 Fri Jun 30 13:07:38 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java Tue Aug 29 11:47:19 2006 @@ -19,10 +19,12 @@ package org.netbeans.modules.java.j2seproject; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; +import java.util.Collections; import javax.swing.Icon; import javax.swing.ImageIcon; import org.netbeans.api.java.classpath.ClassPath; @@ -31,8 +33,6 @@ import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectInformation; import org.netbeans.api.project.ProjectManager; -import org.netbeans.api.project.SourceGroup; -import org.netbeans.api.project.Sources; import org.netbeans.api.project.ant.AntArtifact; import org.netbeans.modules.java.j2seproject.classpath.ClassPathProviderImpl; import org.netbeans.modules.java.j2seproject.classpath.J2SEProjectClassPathExtender; @@ -59,6 +59,7 @@ import org.netbeans.spi.project.support.ant.GeneratedFilesHelper; import org.netbeans.spi.project.support.ant.ProjectXmlSavedHook; import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.netbeans.spi.project.support.ant.PropertyProvider; import org.netbeans.spi.project.support.ant.PropertyUtils; import org.netbeans.spi.project.support.ant.ReferenceHelper; import org.netbeans.spi.project.ui.PrivilegedTemplates; @@ -66,6 +67,7 @@ import org.netbeans.spi.project.ui.RecommendedTemplates; import org.openide.ErrorManager; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; import org.openide.util.Mutex; import org.openide.util.Utilities; @@ -126,12 +128,51 @@ } private PropertyEvaluator createEvaluator() { - // XXX might need to use a custom evaluator to handle active platform substitutions... TBD // It is currently safe to not use the UpdateHelper for PropertyEvaluator; UH.getProperties() delegates to APH - return helper.getStandardPropertyEvaluator(); + // Adapted from APH.getStandardPropertyEvaluator (delegates to ProjectProperties): + PropertyEvaluator baseEval1 = PropertyUtils.sequentialPropertyEvaluator( + helper.getStockPropertyPreprovider(), + helper.getPropertyProvider(J2SEConfigurationProvider.CONFIG_PROPS_PATH)); + PropertyEvaluator baseEval2 = PropertyUtils.sequentialPropertyEvaluator( + helper.getStockPropertyPreprovider(), + helper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH)); + return PropertyUtils.sequentialPropertyEvaluator( + helper.getStockPropertyPreprovider(), + helper.getPropertyProvider(J2SEConfigurationProvider.CONFIG_PROPS_PATH), + new ConfigPropertyProvider(baseEval1, "nbproject/private/configs", helper), // NOI18N + helper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH), + PropertyUtils.userPropertiesProvider(baseEval2, + "user.properties.file", FileUtil.toFile(getProjectDirectory())), // NOI18N + new ConfigPropertyProvider(baseEval1, "nbproject/configs", helper), // NOI18N + helper.getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH)); + } + private static final class ConfigPropertyProvider extends PropertyUtils.DelegatingPropertyProvider implements PropertyChangeListener { + private final PropertyEvaluator baseEval; + private final String prefix; + private final AntProjectHelper helper; + public ConfigPropertyProvider(PropertyEvaluator baseEval, String prefix, AntProjectHelper helper) { + super(computeDelegate(baseEval, prefix, helper)); + this.baseEval = baseEval; + this.prefix = prefix; + this.helper = helper; + baseEval.addPropertyChangeListener(this); + } + public void propertyChange(PropertyChangeEvent ev) { + if (J2SEConfigurationProvider.PROP_CONFIG.equals(ev.getPropertyName())) { + setDelegate(computeDelegate(baseEval, prefix, helper)); + } + } + private static PropertyProvider computeDelegate(PropertyEvaluator baseEval, String prefix, AntProjectHelper helper) { + String config = baseEval.getProperty(J2SEConfigurationProvider.PROP_CONFIG); + if (config != null) { + return helper.getPropertyProvider(prefix + "/" + config + ".properties"); // NOI18N + } else { + return PropertyUtils.fixedPropertyProvider(Collections.emptyMap()); + } + } } - PropertyEvaluator evaluator() { + public PropertyEvaluator evaluator() { return eval; } @@ -139,7 +180,7 @@ return this.refHelper; } - UpdateHelper getUpdateHelper() { + public UpdateHelper getUpdateHelper() { return this.updateHelper; } @@ -179,6 +220,7 @@ cpMod, this, // never cast an externally obtained Project to J2SEProject - use lookup instead new J2SEProjectOperations(this), + new J2SEConfigurationProvider(this), new J2SEProjectWebServicesSupportProvider() }); } Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl:1.75 java/j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl:1.74.2.3 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl:1.75 Fri Jun 30 13:07:41 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/resources/build-impl.xsl Mon Aug 21 13:33:35 2006 @@ -77,6 +77,8 @@ -pre-init + + @@ -91,6 +93,7 @@ -pre-init,-init-private,-init-user + Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/ActionFilterNode.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/ActionFilterNode.java:1.10 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/ActionFilterNode.java:1.9.10.2 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/ActionFilterNode.java:1.10 Fri Jun 30 13:07:41 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/ActionFilterNode.java Mon Aug 21 14:14:18 2006 @@ -80,13 +80,13 @@ * @return ActionFilterNode */ static ActionFilterNode create (Node original, UpdateHelper helper, String classPathId, String entryId) { - DataObject dobj = (DataObject) original.getLookup().lookup(DataObject.class); + DataObject dobj = original.getLookup().lookup(DataObject.class); assert dobj != null; FileObject root = dobj.getPrimaryFile(); Lookup lkp = new ProxyLookup (new Lookup[] {original.getLookup(), helper == null ? Lookups.singleton (new JavadocProvider(root,root)) : - Lookups.fixed (new Object[] {new Removable (helper, classPathId, entryId), - new JavadocProvider(root,root)})}); + Lookups.fixed(new Removable(helper, classPathId, entryId), + new JavadocProvider(root,root))}); return new ActionFilterNode (original, helper == null ? MODE_PACKAGE : MODE_ROOT, root, lkp); } @@ -152,7 +152,7 @@ return actionCache; } - private static class ActionFilterChildren extends Children { + private static class ActionFilterChildren extends FilterNode.Children { private final int mode; private final FileObject cpRoot; @@ -163,25 +163,24 @@ this.cpRoot = cpRooot; } - protected Node[] createNodes(Object key) { - Node n = (Node) key; + protected Node[] createNodes(Node n) { switch (mode) { case MODE_ROOT: case MODE_PACKAGE: - DataObject dobj = (DataObject) n.getCookie(org.openide.loaders.DataObject.class); + DataObject dobj = n.getCookie(DataObject.class); if (dobj == null) { assert false : "DataNode without DataObject in Lookup"; //NOI18N return new Node[0]; } else if (dobj.getPrimaryFile().isFolder()) { - return new Node[] {new ActionFilterNode ((Node)key, MODE_PACKAGE,cpRoot,dobj.getPrimaryFile())}; + return new Node[] {new ActionFilterNode(n, MODE_PACKAGE, cpRoot, dobj.getPrimaryFile())}; } else { - return new Node[] {new ActionFilterNode ((Node)key, MODE_FILE,cpRoot,dobj.getPrimaryFile())}; + return new Node[] {new ActionFilterNode(n, MODE_FILE, cpRoot, dobj.getPrimaryFile())}; } case MODE_FILE: case MODE_FILE_CONTENT: - return new Node[] {new ActionFilterNode ((Node)key, MODE_FILE_CONTENT)}; + return new Node[] {new ActionFilterNode(n, MODE_FILE_CONTENT)}; default: assert false : "Unknown mode"; //NOI18N return new Node[0]; Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java:1.19 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java:1.17.14.2 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java:1.19 Fri Aug 11 06:21:03 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java Mon Aug 21 13:33:36 2006 @@ -514,6 +514,7 @@ actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_RUN, bundle.getString("LBL_RunAction_Name"), null)); // NOI18N actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_DEBUG, bundle.getString("LBL_DebugAction_Name"), null)); // NOI18N actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_TEST, bundle.getString("LBL_TestAction_Name"), null)); // NOI18N + actions.add(CommonProjectActions.setProjectConfigurationAction()); actions.add(null); actions.add(CommonProjectActions.setAsMainProjectAction()); actions.add(CommonProjectActions.openSubprojectsAction()); Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/Bundle.properties diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/Bundle.properties:1.81 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/Bundle.properties:1.80.6.5 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/Bundle.properties:1.81 Fri Jun 30 13:07:42 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/Bundle.properties Tue Aug 29 09:50:30 2006 @@ -485,3 +485,12 @@ TXT_ModifiedTitle=Edit Project Properties MSG_CustomizerLibraries_CompileCpMessage=Compile-time libraries are propagated to all library categories. + +CustomizerRun.configLabel=&Configuration: +CustomizerRun.configNew=&New... +CustomizerRun.configDelete=&Delete +CustomizerRun.default= +CustomizerRun.input.prompt=Profile Name: +CustomizerRun.input.title=Create New Profile +# {0} - name of configuration +CustomizerRun.input.duplicate=Configuration {0} already exists. Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.form diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.form:1.12 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.form:1.12.68.1 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.form:1.12 Wed Mar 23 05:07:37 2005 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.form Wed Jun 14 18:14:16 2006 @@ -3,6 +3,7 @@
+ @@ -11,191 +12,291 @@ - - - - - - - - - - - - + - + - - - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.java:1.21 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.java:1.20.68.7 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.java:1.21 Fri Jun 30 13:07:43 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/CustomizerRun.java Tue Aug 29 10:31:43 2006 @@ -19,49 +19,145 @@ package org.netbeans.modules.java.j2seproject.ui.customizer; +import java.awt.Component; import java.awt.Dialog; +import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.io.File; +import java.text.Collator; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import org.netbeans.api.project.Project; import org.netbeans.modules.java.j2seproject.J2SEProject; import org.netbeans.modules.java.j2seproject.SourceRoots; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.awt.MouseUtils; -import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; +import org.openide.util.Utilities; - -/** - * - * @author phrebejk - */ public class CustomizerRun extends JPanel implements HelpCtx.Provider { private J2SEProject project; + private JTextField[] data; + private JLabel[] dataLabels; + private String[] keys; + private Map/*|null*/> configs; + J2SEProjectProperties uiProperties; + public CustomizerRun( J2SEProjectProperties uiProperties ) { + this.uiProperties = uiProperties; initComponents(); this.project = uiProperties.getProject(); - jTextFieldMainClass.setDocument( uiProperties.MAIN_CLASS_MODEL ); - jTextFieldArgs.setDocument( uiProperties.APPLICATION_ARGS_MODEL ); - jTextVMOptions.setDocument( uiProperties.RUN_JVM_ARGS_MODEL ); - jTextWorkingDirectory.setDocument( uiProperties.RUN_WORK_DIR_MODEL ); - + configs = uiProperties.RUN_CONFIGS; + + data = new JTextField[] { + jTextFieldMainClass, + jTextFieldArgs, + jTextVMOptions, + jTextWorkingDirectory, + }; + dataLabels = new JLabel[] { + jLabelMainClass, + jLabelArgs, + jLabelVMOptions, + jLabelWorkingDirectory, + }; + keys = new String[] { + J2SEProjectProperties.MAIN_CLASS, + J2SEProjectProperties.APPLICATION_ARGS, + J2SEProjectProperties.RUN_JVM_ARGS, + J2SEProjectProperties.RUN_WORK_DIR, + }; + assert data.length == keys.length; + + configChanged(uiProperties.activeConfig); + + configCombo.setRenderer(new DefaultListCellRenderer() { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + String config = (String) value; + String label; + if (config == null) { + // uninitialized? + label = null; + } else if (config.length() > 0) { + Map m = configs.get(config); + label = m != null ? m.get("$label") : /* temporary? */ null; + if (label == null) { + label = config; + } + } else { + label = NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.default"); + } + return super.getListCellRendererComponent(list, label, index, isSelected, cellHasFocus); + } + }); + + for (int i = 0; i < data.length; i++) { + final JTextField field = data[i]; + final String prop = keys[i]; + final JLabel label = dataLabels[i]; + field.getDocument().addDocumentListener(new DocumentListener() { + Font basefont = label.getFont(); + Font boldfont = basefont.deriveFont(Font.BOLD); + { + updateFont(); + } + public void insertUpdate(DocumentEvent e) { + changed(); + } + public void removeUpdate(DocumentEvent e) { + changed(); + } + public void changedUpdate(DocumentEvent e) {} + void changed() { + String config = (String) configCombo.getSelectedItem(); + if (config.length() == 0) { + config = null; + } + String v = field.getText(); + if (v != null && config != null && v.equals(configs.get(null).get(prop))) { + // default value, do not store as such + v = null; + } + configs.get(config).put(prop, v); + updateFont(); + } + void updateFont() { + String v = field.getText(); + String config = (String) configCombo.getSelectedItem(); + if (config.length() == 0) { + config = null; + } + String def = configs.get(null).get(prop); + label.setFont(config != null && !Utilities.compareObjects(v != null ? v : "", def != null ? def : "") ? boldfont : basefont); + } + }); + } + jButtonMainClass.addActionListener( new MainClassListener( project.getSourceRoots(), jTextFieldMainClass ) ); } @@ -78,6 +174,13 @@ private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; + configSep = new javax.swing.JSeparator(); + configPanel = new javax.swing.JPanel(); + configLabel = new javax.swing.JLabel(); + configCombo = new javax.swing.JComboBox(); + configNew = new javax.swing.JButton(); + configDel = new javax.swing.JButton(); + mainPanel = new javax.swing.JPanel(); jLabelMainClass = new javax.swing.JLabel(); jTextFieldMainClass = new javax.swing.JTextField(); jButtonMainClass = new javax.swing.JButton(); @@ -92,50 +195,113 @@ setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 0, 6, 0); + add(configSep, gridBagConstraints); + + configPanel.setLayout(new java.awt.GridBagLayout()); + + configLabel.setLabelFor(configCombo); + org.openide.awt.Mnemonics.setLocalizedText(configLabel, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.configLabel")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 0); + configPanel.add(configLabel, gridBagConstraints); + + configCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "" })); + configCombo.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + configComboActionPerformed(evt); + } + }); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 0); + configPanel.add(configCombo, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(configNew, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.configNew")); // NOI18N + configNew.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + configNewActionPerformed(evt); + } + }); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 0); + configPanel.add(configNew, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(configDel, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.configDelete")); // NOI18N + configDel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + configDelActionPerformed(evt); + } + }); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(2, 6, 2, 0); + configPanel.add(configDel, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 0, 6, 0); + add(configPanel, gridBagConstraints); + + mainPanel.setLayout(new java.awt.GridBagLayout()); + jLabelMainClass.setLabelFor(jTextFieldMainClass); - org.openide.awt.Mnemonics.setLocalizedText(jLabelMainClass, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_MainClass_JLabel")); + org.openide.awt.Mnemonics.setLocalizedText(jLabelMainClass, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_MainClass_JLabel")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0); - add(jLabelMainClass, gridBagConstraints); + mainPanel.add(jLabelMainClass, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 12, 5, 0); - add(jTextFieldMainClass, gridBagConstraints); - jTextFieldMainClass.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getBundle(CustomizerRun.class).getString("AD_jTextFieldMainClass")); + mainPanel.add(jTextFieldMainClass, gridBagConstraints); + jTextFieldMainClass.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getBundle(CustomizerRun.class).getString("AD_jTextFieldMainClass")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(jButtonMainClass, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_MainClass_JButton")); + org.openide.awt.Mnemonics.setLocalizedText(jButtonMainClass, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_MainClass_JButton")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 6, 5, 0); - add(jButtonMainClass, gridBagConstraints); - jButtonMainClass.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getBundle(CustomizerRun.class).getString("AD_jButtonMainClass")); + mainPanel.add(jButtonMainClass, gridBagConstraints); + jButtonMainClass.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getBundle(CustomizerRun.class).getString("AD_jButtonMainClass")); // NOI18N jLabelArgs.setLabelFor(jTextFieldArgs); - org.openide.awt.Mnemonics.setLocalizedText(jLabelArgs, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_Args_JLabel")); + org.openide.awt.Mnemonics.setLocalizedText(jLabelArgs, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_Args_JLabel")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 0, 12, 0); - add(jLabelArgs, gridBagConstraints); + mainPanel.add(jLabelArgs, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 12, 12, 0); - add(jTextFieldArgs, gridBagConstraints); - jTextFieldArgs.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getBundle(CustomizerRun.class).getString("AD_jTextFieldArgs")); + mainPanel.add(jTextFieldArgs, gridBagConstraints); + jTextFieldArgs.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getBundle(CustomizerRun.class).getString("AD_jTextFieldArgs")); // NOI18N jLabelWorkingDirectory.setLabelFor(jTextWorkingDirectory); - org.openide.awt.Mnemonics.setLocalizedText(jLabelWorkingDirectory, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_Working_Directory")); + org.openide.awt.Mnemonics.setLocalizedText(jLabelWorkingDirectory, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_Working_Directory")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridy = 2; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0); - add(jLabelWorkingDirectory, gridBagConstraints); + mainPanel.add(jLabelWorkingDirectory, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridy = 2; @@ -143,10 +309,11 @@ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 12, 5, 0); - add(jTextWorkingDirectory, gridBagConstraints); - jTextWorkingDirectory.getAccessibleContext().setAccessibleDescription(java.util.ResourceBundle.getBundle("org/netbeans/modules/java/j2seproject/ui/customizer/Bundle").getString("AD_CustomizeRun_Run_Working_Directory ")); + mainPanel.add(jTextWorkingDirectory, gridBagConstraints); + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/netbeans/modules/java/j2seproject/ui/customizer/Bundle"); // NOI18N + jTextWorkingDirectory.getAccessibleContext().setAccessibleDescription(bundle.getString("AD_CustomizeRun_Run_Working_Directory ")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(jButtonWorkingDirectoryBrowse, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_Working_Directory_Browse")); + org.openide.awt.Mnemonics.setLocalizedText(jButtonWorkingDirectoryBrowse, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_Working_Directory_Browse")); // NOI18N jButtonWorkingDirectoryBrowse.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButtonWorkingDirectoryBrowseActionPerformed(evt); @@ -158,25 +325,25 @@ gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER; gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 6, 5, 0); - add(jButtonWorkingDirectoryBrowse, gridBagConstraints); - jButtonWorkingDirectoryBrowse.getAccessibleContext().setAccessibleDescription(java.util.ResourceBundle.getBundle("org/netbeans/modules/java/j2seproject/ui/customizer/Bundle").getString("AD_CustomizeRun_Run_Working_Directory_Browse")); + mainPanel.add(jButtonWorkingDirectoryBrowse, gridBagConstraints); + jButtonWorkingDirectoryBrowse.getAccessibleContext().setAccessibleDescription(bundle.getString("AD_CustomizeRun_Run_Working_Directory_Browse")); // NOI18N jLabelVMOptions.setLabelFor(jTextVMOptions); - org.openide.awt.Mnemonics.setLocalizedText(jLabelVMOptions, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_VM_Options")); + org.openide.awt.Mnemonics.setLocalizedText(jLabelVMOptions, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_VM_Options")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 0); - add(jLabelVMOptions, gridBagConstraints); + mainPanel.add(jLabelVMOptions, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 12, 0, 0); - add(jTextVMOptions, gridBagConstraints); - jTextVMOptions.getAccessibleContext().setAccessibleDescription(java.util.ResourceBundle.getBundle("org/netbeans/modules/java/j2seproject/ui/customizer/Bundle").getString("AD_CustomizeRun_Run_VM_Options")); + mainPanel.add(jTextVMOptions, gridBagConstraints); + jTextVMOptions.getAccessibleContext().setAccessibleDescription(bundle.getString("AD_CustomizeRun_Run_VM_Options")); // NOI18N jLabelVMOptionsExample.setLabelFor(jTextFieldMainClass); - org.openide.awt.Mnemonics.setLocalizedText(jLabelVMOptionsExample, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_VM_Options_Example")); + org.openide.awt.Mnemonics.setLocalizedText(jLabelVMOptionsExample, org.openide.util.NbBundle.getMessage(CustomizerRun.class, "LBL_CustomizeRun_Run_VM_Options_Example")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 4; @@ -185,11 +352,61 @@ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weighty = 1.0; gridBagConstraints.insets = new java.awt.Insets(0, 12, 12, 0); - add(jLabelVMOptionsExample, gridBagConstraints); - jLabelVMOptionsExample.getAccessibleContext().setAccessibleDescription(java.util.ResourceBundle.getBundle("org/netbeans/modules/java/j2seproject/ui/customizer/Bundle").getString("LBL_CustomizeRun_Run_VM_Options_Example")); + mainPanel.add(jLabelVMOptionsExample, gridBagConstraints); + jLabelVMOptionsExample.getAccessibleContext().setAccessibleDescription(bundle.getString("LBL_CustomizeRun_Run_VM_Options_Example")); // NOI18N - } - // //GEN-END:initComponents + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(6, 0, 6, 0); + add(mainPanel, gridBagConstraints); + + }// //GEN-END:initComponents + + private void configDelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configDelActionPerformed + String config = (String) configCombo.getSelectedItem(); + assert config != null; + configs.put(config, null); + configChanged(null); + uiProperties.activeConfig = null; + }//GEN-LAST:event_configDelActionPerformed + + private void configNewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configNewActionPerformed + NotifyDescriptor.InputLine d = new NotifyDescriptor.InputLine( + NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.input.prompt"), + NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.input.title")); + if (DialogDisplayer.getDefault().notify(d) != NotifyDescriptor.OK_OPTION) { + return; + } + String name = d.getInputText(); + String config = name.replaceAll("[^a-zA-Z0-9_.-]", "_"); // NOI18N + if (configs.get(config) != null) { + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( + NbBundle.getMessage(CustomizerRun.class, "CustomizerRun.input.duplicate", config), + NotifyDescriptor.WARNING_MESSAGE)); + return; + } + Map m = new HashMap(); + if (!name.equals(config)) { + m.put("$label", name); // NOI18N + } + configs.put(config, m); + configChanged(config); + uiProperties.activeConfig = config; + }//GEN-LAST:event_configNewActionPerformed + + private void configComboActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_configComboActionPerformed + String config = (String) configCombo.getSelectedItem(); + if (config.length() == 0) { + config = null; + } + configChanged(config); + uiProperties.activeConfig = config; + }//GEN-LAST:event_configComboActionPerformed private void jButtonWorkingDirectoryBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonWorkingDirectoryBrowseActionPerformed JFileChooser chooser = new JFileChooser(); @@ -208,9 +425,55 @@ jTextWorkingDirectory.setText(file.getAbsolutePath()); } }//GEN-LAST:event_jButtonWorkingDirectoryBrowseActionPerformed + + private void configChanged(String activeConfig) { + DefaultComboBoxModel model = new DefaultComboBoxModel(); + model.addElement(""); + SortedSet alphaConfigs = new TreeSet(new Comparator() { + Collator coll = Collator.getInstance(); + public int compare(String s1, String s2) { + return coll.compare(label(s1), label(s2)); + } + private String label(String c) { + Map m = configs.get(c); + String label = m.get("$label"); // NOI18N + return label != null ? label : c; + } + }); + for (Map.Entry> entry : configs.entrySet()) { + String config = entry.getKey(); + if (config != null && entry.getValue() != null) { + alphaConfigs.add(config); + } + } + for (String c : alphaConfigs) { + model.addElement(c); + } + configCombo.setModel(model); + configCombo.setSelectedItem(activeConfig != null ? activeConfig : ""); + Map m = configs.get(activeConfig); + Map def = configs.get(null); + if (m != null) { + for (int i = 0; i < data.length; i++) { + String v = m.get(keys[i]); + if (v == null) { + // display default value + v = def.get(keys[i]); + } + data[i].setText(v); + } + } // else ?? + configDel.setEnabled(activeConfig != null); + } // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox configCombo; + private javax.swing.JButton configDel; + private javax.swing.JLabel configLabel; + private javax.swing.JButton configNew; + private javax.swing.JPanel configPanel; + private javax.swing.JSeparator configSep; private javax.swing.JButton jButtonMainClass; private javax.swing.JButton jButtonWorkingDirectoryBrowse; private javax.swing.JLabel jLabelArgs; @@ -222,6 +485,7 @@ private javax.swing.JTextField jTextFieldMainClass; private javax.swing.JTextField jTextVMOptions; private javax.swing.JTextField jTextWorkingDirectory; + private javax.swing.JPanel mainPanel; // End of variables declaration//GEN-END:variables Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SECompositePanelProvider.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SECompositePanelProvider.java:1.2 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SECompositePanelProvider.java:1.1.6.2 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SECompositePanelProvider.java:1.2 Fri Jun 30 13:07:43 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SECompositePanelProvider.java Mon Aug 21 13:33:39 2006 @@ -36,9 +36,6 @@ * @author mkleint */ public class J2SECompositePanelProvider implements ProjectCustomizer.CompositeCategoryProvider { - // Names of categories - private static final String BUILD_CATEGORY = "BuildCategory"; - private static final String RUN_CATEGORY = "RunCategory"; private static final String SOURCES = "Sources"; static final String LIBRARIES = "Libraries"; @@ -47,7 +44,7 @@ // private static final String BUILD_TESTS = "BuildTests"; private static final String JAR = "Jar"; private static final String JAVADOC = "Javadoc"; - private static final String RUN = "Run"; + public static final String RUN = "Run"; // private static final String RUN_TESTS = "RunTests"; private static final String WEBSERVICECLIENTS = "WebServiceClients"; Index: java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectProperties.java diff -u java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectProperties.java:1.58 java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectProperties.java:1.57.10.8 --- java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectProperties.java:1.58 Fri Jun 30 13:07:43 2006 +++ java/j2seproject/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectProperties.java Tue Aug 29 11:47:20 2006 @@ -23,12 +23,15 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.TreeMap; import java.util.Vector; import javax.swing.ButtonModel; import javax.swing.ComboBoxModel; @@ -40,7 +43,6 @@ import javax.swing.text.BadLocationException; import javax.swing.text.Document; import org.netbeans.api.project.ProjectManager; -import org.netbeans.api.queries.CollocationQuery; import org.netbeans.modules.java.j2seproject.J2SEProject; import org.netbeans.modules.java.j2seproject.J2SEProjectUtil; import org.netbeans.modules.java.j2seproject.SourceRoots; @@ -50,7 +52,6 @@ import org.netbeans.spi.project.support.ant.EditableProperties; import org.netbeans.spi.project.support.ant.GeneratedFilesHelper; import org.netbeans.spi.project.support.ant.PropertyEvaluator; -import org.netbeans.spi.project.support.ant.PropertyUtils; import org.netbeans.spi.project.support.ant.ReferenceHelper; import org.netbeans.spi.project.support.ant.ui.StoreGroup; import org.openide.DialogDisplayer; @@ -58,10 +59,10 @@ import org.openide.NotifyDescriptor; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; -import org.openide.modules.SpecificationVersion; import org.openide.util.Mutex; import org.openide.util.MutexException; import org.openide.util.NbBundle; +import org.openide.util.Utilities; /** * @author Petr Hrebejk @@ -191,11 +192,8 @@ Document JAVADOC_ADDITIONALPARAM_MODEL; // CustomizerRun - Document MAIN_CLASS_MODEL; - Document APPLICATION_ARGS_MODEL; - Document RUN_JVM_ARGS_MODEL; - Document RUN_WORK_DIR_MODEL; - + Map/*|null*/> RUN_CONFIGS; + String activeConfig; // CustomizerRunTest @@ -289,10 +287,8 @@ JAVADOC_ADDITIONALPARAM_MODEL = projectGroup.createStringDocument( evaluator, JAVADOC_ADDITIONALPARAM ); // CustomizerRun - MAIN_CLASS_MODEL = projectGroup.createStringDocument( evaluator, MAIN_CLASS ); - APPLICATION_ARGS_MODEL = privateGroup.createStringDocument( evaluator, APPLICATION_ARGS ); - RUN_JVM_ARGS_MODEL = projectGroup.createStringDocument( evaluator, RUN_JVM_ARGS ); - RUN_WORK_DIR_MODEL = privateGroup.createStringDocument( evaluator, RUN_WORK_DIR ); + RUN_CONFIGS = readRunConfigs(); + activeConfig = evaluator.getProperty("config"); } @@ -363,6 +359,15 @@ projectGroup.store( projectProperties ); privateGroup.store( privateProperties ); + storeRunConfigs(RUN_CONFIGS, projectProperties, privateProperties); + EditableProperties ep = updateHelper.getProperties("nbproject/private/config.properties"); + if (activeConfig == null) { + ep.remove("config"); + } else { + ep.setProperty("config", activeConfig); + } + updateHelper.putProperties("nbproject/private/config.properties", ep); + //Hotfix of the issue #70058 //Should use the StoreGroup when the StoreGroup SPI will be extended to allow false default value in ToggleButtonModel //Save javac.debug @@ -387,10 +392,6 @@ projectProperties.remove( NO_DEPENDENCIES ); // Remove the property completely if not set } - if ( getDocumentText( RUN_WORK_DIR_MODEL ).trim().equals( "" ) ) { // NOI18N - privateProperties.remove( RUN_WORK_DIR ); // Remove the property completely if not set - } - storeAdditionalProperties(projectProperties); // Store the property changes into the project @@ -401,8 +402,8 @@ private void storeAdditionalProperties(EditableProperties projectProperties) { for (Iterator i = additionalProperties.keySet().iterator(); i.hasNext();) { - Object key = i.next(); - projectProperties.put(key, additionalProperties.get(key)); + String key = (String) i.next(); + projectProperties.put(key, (String) additionalProperties.get(key)); } } @@ -464,7 +465,6 @@ changed = true; } } - File projDir = FileUtil.toFile(updateHelper.getAntProjectHelper().getProjectDirectory()); for( Iterator it = added.iterator(); it.hasNext(); ) { ClassPathSupport.Item item = (ClassPathSupport.Item)it.next(); if (item.getType() == ClassPathSupport.Item.TYPE_LIBRARY && !item.isBroken()) { @@ -553,6 +553,104 @@ JToggleButton.ToggleButtonModel bm = new JToggleButton.ToggleButtonModel(); bm.setSelected(isSelected ); return bm; + } + + /** + * A mess. + */ + Map> readRunConfigs() { + Map> m = new TreeMap>(new Comparator() { + public int compare(String s1, String s2) { + return s1 != null ? (s2 != null ? s1.compareTo(s2) : 1) : (s2 != null ? -1 : 0); + } + }); + Map def = new TreeMap(); + for (String prop : new String[] {MAIN_CLASS, APPLICATION_ARGS, RUN_JVM_ARGS, RUN_WORK_DIR}) { + String v = updateHelper.getProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH).getProperty(prop); + if (v == null) { + v = updateHelper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH).getProperty(prop); + } + if (v != null) { + def.put(prop, v); + } + } + m.put(null, def); + FileObject configs = project.getProjectDirectory().getFileObject("nbproject/configs"); + if (configs != null) { + for (FileObject kid : configs.getChildren()) { + if (!kid.hasExt("properties")) { + continue; + } + m.put(kid.getName(), new TreeMap(updateHelper.getProperties(FileUtil.getRelativePath(project.getProjectDirectory(), kid)))); + } + } + configs = project.getProjectDirectory().getFileObject("nbproject/private/configs"); + if (configs != null) { + for (FileObject kid : configs.getChildren()) { + if (!kid.hasExt("properties")) { + continue; + } + Map c = m.get(kid.getName()); + if (c == null) { + continue; + } + c.putAll(new HashMap(updateHelper.getProperties(FileUtil.getRelativePath(project.getProjectDirectory(), kid)))); + } + } + //System.err.println("readRunConfigs: " + m); + return m; + } + + /** + * A royal mess. + */ + void storeRunConfigs(Map/*|null*/> configs, + EditableProperties projectProperties, EditableProperties privateProperties) throws IOException { + //System.err.println("storeRunConfigs: " + configs); + Map def = configs.get(null); + for (String prop : new String[] {MAIN_CLASS, APPLICATION_ARGS, RUN_JVM_ARGS, RUN_WORK_DIR}) { + String v = def.get(prop); + EditableProperties ep = (prop.equals(APPLICATION_ARGS) || prop.equals(RUN_WORK_DIR)) ? + privateProperties : projectProperties; + if (!Utilities.compareObjects(v, ep.getProperty(prop))) { + if (v != null && v.length() > 0) { + ep.setProperty(prop, v); + } else { + ep.remove(prop); + } + } + } + for (Map.Entry> entry : configs.entrySet()) { + String config = entry.getKey(); + if (config == null) { + continue; + } + String sharedPath = "nbproject/configs/" + config + ".properties"; // NOI18N + String privatePath = "nbproject/private/configs/" + config + ".properties"; // NOI18N + Map c = entry.getValue(); + if (c == null) { + updateHelper.putProperties(sharedPath, null); + updateHelper.putProperties(privatePath, null); + continue; + } + for (Map.Entry entry2 : c.entrySet()) { + String prop = entry2.getKey(); + String v = entry2.getValue(); + String path = (prop.equals(APPLICATION_ARGS) || prop.equals(RUN_WORK_DIR)) ? + privatePath : sharedPath; + EditableProperties ep = updateHelper.getProperties(path); + if (!Utilities.compareObjects(v, ep.getProperty(prop))) { + if (v != null && (v.length() > 0 || (def.get(prop) != null && def.get(prop).length() > 0))) { + ep.setProperty(prop, v); + } else { + ep.remove(prop); + } + updateHelper.putProperties(path, ep); + } + } + // Make sure the definition file is always created, even if it is empty. + updateHelper.putProperties(sharedPath, updateHelper.getProperties(sharedPath)); + } } } Index: java/j2seproject/test/unit/src/org/netbeans/modules/java/j2seproject/J2SEConfigurationProviderTest.java diff -u /dev/null java/j2seproject/test/unit/src/org/netbeans/modules/java/j2seproject/J2SEConfigurationProviderTest.java:1.1.2.6 --- /dev/null Thu Aug 31 13:03:58 2006 +++ java/j2seproject/test/unit/src/org/netbeans/modules/java/j2seproject/J2SEConfigurationProviderTest.java Tue Aug 29 11:47:20 2006 @@ -0,0 +1,214 @@ +/* + * 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-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.modules.java.j2seproject; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.project.ProjectConfiguration; +import org.netbeans.spi.project.ProjectConfigurationProvider; +import org.netbeans.spi.project.support.ant.EditableProperties; +import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * @author Jesse Glick + */ +public class J2SEConfigurationProviderTest extends NbTestCase { + + public J2SEConfigurationProviderTest(String name) { + super(name); + } + + private FileObject d; + private J2SEProject p; + private ProjectConfigurationProvider pcp; + + protected void setUp() throws Exception { + super.setUp(); + clearWorkDir(); + d = J2SEProjectGenerator.createProject(getWorkDir(), "test", null, null).getProjectDirectory(); + p = (J2SEProject) ProjectManager.getDefault().findProject(d); + pcp = p.getLookup().lookup(ProjectConfigurationProvider.class); + assertNotNull(pcp); + Locale.setDefault(Locale.US); + } + + public void testInitialState() throws Exception { + assertEquals(1, pcp.getConfigurations().size()); + assertNotNull(pcp.getActiveConfiguration()); + assertEquals(pcp.getActiveConfiguration(), pcp.getConfigurations().iterator().next()); + assertEquals("", pcp.getActiveConfiguration().getDisplayName()); + assertTrue(pcp.hasCustomizer()); + } + + public void testConfigurations() throws Exception { + TestListener l = new TestListener(); + pcp.addPropertyChangeListener(l); + Properties p = new Properties(); + p.setProperty("$label", "Debug"); + write(p, d, "nbproject/configs/debug.properties"); + p = new Properties(); + p.setProperty("$label", "Release"); + write(p, d, "nbproject/configs/release.properties"); + write(new Properties(), d, "nbproject/configs/misc.properties"); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATIONS), l.events()); + List configs = new ArrayList(getConfigurations(pcp)); + assertEquals(4, configs.size()); + assertEquals("", configs.get(0).getDisplayName()); + assertEquals("Debug", configs.get(1).getDisplayName()); + assertEquals("misc", configs.get(2).getDisplayName()); + assertEquals("Release", configs.get(3).getDisplayName()); + assertEquals(Collections.emptySet(), l.events()); + d.getFileObject("nbproject/configs/debug.properties").delete(); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATIONS), l.events()); + configs = new ArrayList(getConfigurations(pcp)); + assertEquals(3, configs.size()); + d.getFileObject("nbproject/configs").delete(); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATIONS), l.events()); + configs = new ArrayList(getConfigurations(pcp)); + assertEquals(1, configs.size()); + write(new Properties(), d, "nbproject/configs/misc.properties"); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATIONS), l.events()); + configs = new ArrayList(getConfigurations(pcp)); + assertEquals(2, configs.size()); + } + + public void testActiveConfiguration() throws Exception { + write(new Properties(), d, "nbproject/configs/debug.properties"); + write(new Properties(), d, "nbproject/configs/release.properties"); + TestListener l = new TestListener(); + pcp.addPropertyChangeListener(l); + ProjectConfiguration def = pcp.getActiveConfiguration(); + assertEquals("", def.getDisplayName()); + List configs = new ArrayList(getConfigurations(pcp)); + assertEquals(3, configs.size()); + ProjectConfiguration c = configs.get(2); + assertEquals("release", c.getDisplayName()); + setActiveConfiguration(pcp, c); + assertEquals(c, pcp.getActiveConfiguration()); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE), l.events()); + setActiveConfiguration(pcp, c); + assertEquals(c, pcp.getActiveConfiguration()); + assertEquals(Collections.emptySet(), l.events()); + setActiveConfiguration(pcp, def); + assertEquals(def, pcp.getActiveConfiguration()); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE), l.events()); + try { + setActiveConfiguration(pcp, null); + fail(); + } catch (IllegalArgumentException x) {/*OK*/} + assertEquals(Collections.emptySet(), l.events()); + try { + setActiveConfiguration(pcp, new ProjectConfiguration() { + public String getDisplayName() { + return "bogus"; + } + }); + fail(); + } catch (IllegalArgumentException x) { + // OK, not in original set + } catch (ClassCastException x) { + // also OK, not of correct type + } + assertEquals(Collections.emptySet(), l.events()); + EditableProperties ep = new EditableProperties(); + ep.setProperty("config", "debug"); + p.getUpdateHelper().putProperties("nbproject/private/config.properties", ep); + assertEquals("debug", pcp.getActiveConfiguration().getDisplayName()); + assertEquals(Collections.singleton(ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE), l.events()); + } + + public void testEvaluator() throws Exception { + PropertyEvaluator eval = p.evaluator(); + TestListener l = new TestListener(); + eval.addPropertyChangeListener(l); + Properties p = new Properties(); + p.setProperty("debug", "true"); + write(p, d, "nbproject/configs/debug.properties"); + p = new Properties(); + p.setProperty("debug", "false"); + write(p, d, "nbproject/configs/release.properties"); + p = new Properties(); + p.setProperty("more", "stuff"); + write(p, d, "nbproject/private/configs/release.properties"); + List configs = new ArrayList(getConfigurations(pcp)); + assertEquals(3, configs.size()); + ProjectConfiguration c = configs.get(1); + assertEquals("debug", c.getDisplayName()); + setActiveConfiguration(pcp, c); + assertEquals(new HashSet(Arrays.asList(new String[] {"config", "debug"})), l.events()); + assertEquals("debug", eval.getProperty("config")); + assertEquals("true", eval.getProperty("debug")); + assertEquals(null, eval.getProperty("more")); + c = configs.get(2); + assertEquals("release", c.getDisplayName()); + setActiveConfiguration(pcp, c); + assertEquals(new HashSet(Arrays.asList(new String[] {"config", "debug", "more"})), l.events()); + assertEquals("release", eval.getProperty("config")); + assertEquals("false", eval.getProperty("debug")); + assertEquals("stuff", eval.getProperty("more")); + c = configs.get(0); + assertEquals("", c.getDisplayName()); + setActiveConfiguration(pcp, c); + assertEquals(new HashSet(Arrays.asList(new String[] {"config", "debug", "more"})), l.events()); + assertEquals(null, eval.getProperty("config")); + assertEquals(null, eval.getProperty("debug")); + assertEquals(null, eval.getProperty("more")); + // XXX test nbproject/private/configs/*.properties + } + + private void write(Properties p, FileObject d, String path) throws IOException { + FileObject f = FileUtil.createData(d, path); + OutputStream os = f.getOutputStream(); + p.store(os, null); + os.close(); + } + + private static final class TestListener implements PropertyChangeListener { + private Set events = new HashSet(); + public void propertyChange(PropertyChangeEvent evt) { + events.add(evt.getPropertyName()); + } + public Set events() { + Set copy = events; + events = new HashSet(); + return copy; + } + } + + private static Collection getConfigurations(ProjectConfigurationProvider pcp) { + return pcp.getConfigurations(); + } + + @SuppressWarnings("unchecked") + private static void setActiveConfiguration(ProjectConfigurationProvider pcp, ProjectConfiguration pc) throws IOException { + ProjectConfigurationProvider _pcp = pcp; + _pcp.setActiveConfiguration(pc); + } + +} Index: java/j2seproject/test/unit/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectPropertiesTest.java diff -u /dev/null java/j2seproject/test/unit/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectPropertiesTest.java:1.1.2.1 --- /dev/null Thu Aug 31 13:03:58 2006 +++ java/j2seproject/test/unit/src/org/netbeans/modules/java/j2seproject/ui/customizer/J2SEProjectPropertiesTest.java Tue Aug 29 11:47:21 2006 @@ -0,0 +1,86 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.modules.java.j2seproject.ui.customizer; + +import java.io.IOException; +import java.util.Map; +import java.util.TreeMap; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.j2seproject.J2SEProject; +import org.netbeans.modules.java.j2seproject.J2SEProjectGenerator; +import org.netbeans.spi.project.support.ant.AntProjectHelper; +import org.netbeans.spi.project.support.ant.EditableProperties; +import org.openide.filesystems.FileUtil; + +public class J2SEProjectPropertiesTest extends NbTestCase { + + public J2SEProjectPropertiesTest(String name) { + super(name); + } + + private J2SEProject p; + private J2SEProjectProperties pp; + + protected void setUp() throws Exception { + super.setUp(); + clearWorkDir(); + J2SEProjectGenerator.createProject(getWorkDir(), "test", null, null); + p = (J2SEProject) ProjectManager.getDefault().findProject(FileUtil.toFileObject(getWorkDir())); + pp = new J2SEProjectProperties(p, p.getUpdateHelper(), p.evaluator(), /* XXX unneeded probably */null, null); + } + + public void testRunConfigs() throws Exception { + Map> m = pp.readRunConfigs(); + assertEquals("{null={run.jvmargs=}}", m.toString()); + // Define a new config and set some arguments. + Map c = new TreeMap(); + c.put("application.args", "foo"); + m.put("foo", c); + storeRunConfigs(m); + m = pp.readRunConfigs(); + assertEquals("{null={run.jvmargs=}, foo={application.args=foo}}", m.toString()); + // Define args in default config. + m.get(null).put("application.args", "bland"); + storeRunConfigs(m); + m = pp.readRunConfigs(); + assertEquals("{null={application.args=bland, run.jvmargs=}, foo={application.args=foo}}", m.toString()); + // Reset to default in foo config. + m.get("foo").put("application.args", null); + storeRunConfigs(m); + m = pp.readRunConfigs(); + assertEquals("{null={application.args=bland, run.jvmargs=}, foo={}}", m.toString()); + // Override as blank in foo config. + m.get("foo").put("application.args", ""); + storeRunConfigs(m); + m = pp.readRunConfigs(); + assertEquals("{null={application.args=bland, run.jvmargs=}, foo={application.args=}}", m.toString()); + } + + private void storeRunConfigs(Map> m) throws IOException { + EditableProperties prj = p.getUpdateHelper().getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); + EditableProperties prv = p.getUpdateHelper().getProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH); + pp.storeRunConfigs(m, prj, prv); + p.getUpdateHelper().putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, prj); + p.getUpdateHelper().putProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH, prv); + ProjectManager.getDefault().saveProject(p); + } + +} Index: projects/projectapi/apichanges.xml diff -u projects/projectapi/apichanges.xml:1.8 projects/projectapi/apichanges.xml:1.7.26.2 --- projects/projectapi/apichanges.xml:1.8 Fri Jun 30 14:27:13 2006 +++ projects/projectapi/apichanges.xml Mon Aug 21 13:33:57 2006 @@ -81,6 +81,25 @@ + + + + Added support for project configurations + + + + + +

+ Added an interface ProjectConfigurationProvider + which can be included in a project's lookup to support + switchable configurations / profiles. +

+
+ + + +
Index: projects/projectapi/manifest.mf diff -u projects/projectapi/manifest.mf:1.13 projects/projectapi/manifest.mf:1.13.10.1 --- projects/projectapi/manifest.mf:1.13 Mon Dec 12 07:40:21 2005 +++ projects/projectapi/manifest.mf Thu Jun 15 13:38:19 2006 @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.projectapi/1 -OpenIDE-Module-Specification-Version: 1.10 +OpenIDE-Module-Specification-Version: 1.11 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties Index: projects/projectapi/src/org/netbeans/api/project/Project.java diff -u projects/projectapi/src/org/netbeans/api/project/Project.java:1.14 projects/projectapi/src/org/netbeans/api/project/Project.java:1.12.10.2 --- projects/projectapi/src/org/netbeans/api/project/Project.java:1.14 Fri Jun 30 14:27:14 2006 +++ projects/projectapi/src/org/netbeans/api/project/Project.java Mon Aug 21 13:33:58 2006 @@ -76,6 +76,7 @@ * *

You might also have e.g.:

*
    + *
  1. {@link org.netbeans.spi.project.ProjectConfigurationProvider}
  2. *
  3. {@link org.netbeans.spi.queries.FileBuiltQueryImplementation}
  4. *
  5. {@link org.netbeans.spi.queries.SharabilityQueryImplementation}
  6. *
  7. ProjectOpenedHook
  8. Index: projects/projectapi/src/org/netbeans/spi/project/ProjectConfiguration.java diff -u /dev/null projects/projectapi/src/org/netbeans/spi/project/ProjectConfiguration.java:1.1.2.3 --- /dev/null Thu Aug 31 13:04:03 2006 +++ projects/projectapi/src/org/netbeans/spi/project/ProjectConfiguration.java Fri Jun 16 12:33:14 2006 @@ -0,0 +1,33 @@ +/* + * 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-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project; + +/** + * Represents one user-selectable configuration of a particular project. + * For example, it might represent a choice of main class and arguments. + * Besides the implementor, only the project UI infrastructure is expected to use this class. + * + * @author Adam Sotona, Jesse Glick + * @since org.netbeans.modules.projectapi/1 1.11 + * @see ProjectConfigurationProvider + */ +public interface ProjectConfiguration { + + /** + * Provides a display name by which this configuration may be identified in the GUI. + * @return a human-visible display name + */ + String getDisplayName(); + +} Index: projects/projectapi/src/org/netbeans/spi/project/ProjectConfigurationProvider.java diff -u /dev/null projects/projectapi/src/org/netbeans/spi/project/ProjectConfigurationProvider.java:1.1.2.9 --- /dev/null Thu Aug 31 13:04:03 2006 +++ projects/projectapi/src/org/netbeans/spi/project/ProjectConfigurationProvider.java Mon Aug 28 15:08:38 2006 @@ -0,0 +1,123 @@ +/* + * 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-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project; + +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.util.Collection; + +/** + * Provider of configurations for a project. + * Should be registered in a project's {@link org.netbeans.api.project.Project#getLookup lookup}. + * Besides the implementor, only the project UI infrastructure is expected to use this class. + * @param C the type of configuration created by this provider + * + * @author Adam Sotona, Jesse Glick + * @since org.netbeans.modules.projectapi/1 1.11 + * @see Project Configurations design document + */ +public interface ProjectConfigurationProvider { + + /** + * Property name for the active configuration. + * Use it when firing a change in the active configuration. + */ + String PROP_CONFIGURATION_ACTIVE = "activeConfiguration"; // NOI18N + + /** + * Property name of the set of configurations. + * Use it when firing a change in the set of configurations. + */ + String PROP_CONFIGURATIONS = "configurations"; // NOI18N + + /** + * Gets a list of configurations. + * Permitted to return different instances from one invocation to the next + * but it is advisable for the "same" instances to compare as equal. + *

    Should be called within {@link org.netbeans.api.project.ProjectManager#mutex read access}. + * @return all available configurations for this project + */ + Collection getConfigurations(); + + /** + * Gets the currently active configuration. + *

    Should be called within {@link org.netbeans.api.project.ProjectManager#mutex read access}. + * @return the active configuration for this project (should be a member of {@link #getConfigurations}, or null only if that is empty) + */ + C getActiveConfiguration(); + + /** + * Sets the active configuration. + * Should fire a change in {@link #PROP_CONFIGURATION_ACTIVE}. + * It should be true afterwards that configuration.equals(getActiveConfiguration()) + * though it might not be true that configuration == getActiveConfiguration(). + *

    + * If possible, the choice of configuration should be persisted for the next IDE session. + * If applicable, the persisted choice should be kept in per-user settings, not shared or versioned. + *

    + *

    Should be called within {@link org.netbeans.api.project.ProjectManager#mutex write access}. + * @param configuration new active configuration + * @throws IllegalArgumentException if the requested configuration is not a member of {@link #getConfigurations} + * @throws IOException if storing the configuration change failed + */ + void setActiveConfiguration(C configuration) throws IllegalArgumentException, IOException; + + /** + * Checks if this project can provide a GUI customizer for its configurations. + * @return true if {@link #customize} may be called + */ + boolean hasCustomizer(); + + /** + * Customize this project's configurations. + * Only permitted if {@link #hasCustomizer} is true. + * May, for example, open the project properties dialog. + */ + void customize(); + + /** + * Indicates if a project action is affected by the choice of configuration. + * If so, a GUI for this action is permitted to show a list of configurations and + * let the user select a configuration to apply to one action invocation only. + * Such a GUI can avoid the need to first select an active configuration and + * then run the action as two steps. + * This is done by including a {@link ProjectConfiguration} in the context passed + * to {@link ActionProvider#invokeAction}. + * A project is free to return false even if the configuration + * might affect the behavior of the action, if it simply does not + * wish for such a GUI to be shown. + *

    + * The likely values of command are those actions + * normally shown in the IDE's tool bar with main project bindings: + * {@link ActionProvider#BUILD}, {@link ActionProvider#REBUILD}, + * {@link ActionProvider#RUN}, and {@link ActionProvider#DEBUG}. + *

    + * @param command one of {@link ActionProvider#getSupportedActions} + * @return true if the named command refers to an action affected by configurations + */ + boolean configurationsAffectAction(String command); + + /** + * Adds a listener to check for changes in {@link #PROP_CONFIGURATION_ACTIVE} or {@link #PROP_CONFIGURATIONS}. + * @param lst a listener to add + */ + void addPropertyChangeListener(PropertyChangeListener lst); + + /** + * Removes a listener. + * @param lst a listener to remove + */ + void removePropertyChangeListener(PropertyChangeListener lst); + +} Index: projects/projectui/arch.xml diff -u projects/projectui/arch.xml:1.16 projects/projectui/arch.xml:1.13.4.2 --- projects/projectui/arch.xml:1.16 Fri Aug 18 04:35:01 2006 +++ projects/projectui/arch.xml Thu Aug 31 13:00:03 2006 @@ -685,6 +685,13 @@ by enhanced UI logger that track what the user is going. +
  9. + + If set to true, main project action toolbar buttons will + under appropriate circumstances display pull-down buttons permitting + the action to be run against a particular nonactive configuration directly. + +
  10. Index: projects/projectui/src/org/netbeans/modules/project/ui/actions/Actions.java diff -u projects/projectui/src/org/netbeans/modules/project/ui/actions/Actions.java:1.30 projects/projectui/src/org/netbeans/modules/project/ui/actions/Actions.java:1.29.12.2 --- projects/projectui/src/org/netbeans/modules/project/ui/actions/Actions.java:1.30 Fri Jun 30 14:27:21 2006 +++ projects/projectui/src/org/netbeans/modules/project/ui/actions/Actions.java Mon Aug 21 13:34:11 2006 @@ -39,6 +39,7 @@ import org.openide.util.Utilities; import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.NodeAction; +import org.openide.util.actions.SystemAction; /** Factory for all kinds of actions used in projectui and *projectuiapi. @@ -353,6 +354,10 @@ new ImageIcon( Utilities.loadImage( "org/netbeans/modules/project/ui/resources/debugProject.gif" ) ) ); //NOI18N a.putValue("iconBase","org/netbeans/modules/project/ui/resources/debugProject.gif"); //NOI18N return a; + } + + public Action setProjectConfigurationAction() { + return SystemAction.get(ActiveConfigAction.class); } } Index: projects/projectui/src/org/netbeans/modules/project/ui/actions/ActiveConfigAction.java diff -u /dev/null projects/projectui/src/org/netbeans/modules/project/ui/actions/ActiveConfigAction.java:1.1.2.8 --- /dev/null Thu Aug 31 13:04:03 2006 +++ projects/projectui/src/org/netbeans/modules/project/ui/actions/ActiveConfigAction.java Mon Aug 21 14:45:45 2006 @@ -0,0 +1,335 @@ +/* + * 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-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.modules.project.ui.actions; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.modules.project.ui.OpenProjectList; +import org.netbeans.spi.project.ProjectConfiguration; +import org.netbeans.spi.project.ProjectConfigurationProvider; +import org.openide.awt.DynamicMenuContent; +import org.openide.awt.Mnemonics; +import org.openide.util.ContextAwareAction; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; +import org.openide.util.Mutex; +import org.openide.util.MutexException; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.util.actions.Presenter; + +/** + * Action permitting selection of a configuration for the main project. + * @author Greg Crawley, Adam Sotona, Jesse Glick + */ +public class ActiveConfigAction extends CallableSystemAction implements ContextAwareAction { + + private static final DefaultComboBoxModel EMPTY_MODEL = new DefaultComboBoxModel(); + private static final Object CUSTOMIZE_ENTRY = new Object(); + + private final PropertyChangeListener lst; + private final JComboBox configListCombo; + private boolean listeningToCombo = true; + + private Project currentProject; + private ProjectConfigurationProvider pcp; + + public ActiveConfigAction() { + super(); + putValue("noIconInMenu", Boolean.TRUE); // NOI18N + configListCombo = new JComboBox(); + configListCombo.setRenderer(new ConfigCellRenderer()); + configListCombo.setToolTipText(org.openide.awt.Actions.cutAmpersand(getName())); + configurationsListChanged(null); + configListCombo.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (!listeningToCombo) { + return; + } + Object o = configListCombo.getSelectedItem(); + if (o == CUSTOMIZE_ENTRY) { + activeConfigurationChanged(pcp != null ? getActiveConfiguration(pcp) : null); + pcp.customize(); + } else if (o != null) { + activeConfigurationSelected((ProjectConfiguration) o); + } + } + }); + lst = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (ProjectConfigurationProvider.PROP_CONFIGURATIONS.equals(evt.getPropertyName())) { + configurationsListChanged(getConfigurations(pcp)); + } else if (ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE.equals(evt.getPropertyName())) { + activeConfigurationChanged(getActiveConfiguration(pcp)); + } + } + }; + activeProjectChanged(OpenProjectList.getDefault().getMainProject()); + OpenProjectList.getDefault().addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (OpenProjectList.PROPERTY_MAIN_PROJECT.equals(evt.getPropertyName())) { + activeProjectChanged(OpenProjectList.getDefault().getMainProject()); + } + } + }); + } + + + private synchronized void configurationsListChanged(Collection configs) { + if (configs == null) { + configListCombo.setModel(EMPTY_MODEL); + configListCombo.setEnabled(false); + } else { + DefaultComboBoxModel model = new DefaultComboBoxModel(configs.toArray()); + if (pcp.hasCustomizer()) { + model.addElement(CUSTOMIZE_ENTRY); + } + configListCombo.setModel(model); + configListCombo.setEnabled(true); + } + if (pcp != null) { + activeConfigurationChanged(getActiveConfiguration(pcp)); + } + } + + private synchronized void activeConfigurationChanged(ProjectConfiguration config) { + listeningToCombo = false; + try { + configListCombo.setSelectedIndex(-1); + if (config != null) { + ComboBoxModel m = configListCombo.getModel(); + for (int i = 0; i < m.getSize(); i++) { + if (config.equals(m.getElementAt(i))) { + configListCombo.setSelectedIndex(i); + break; + } + } + } + } finally { + listeningToCombo = true; + } + } + + private synchronized void activeConfigurationSelected(ProjectConfiguration cfg) { + if (pcp != null && cfg != null && !cfg.equals(getActiveConfiguration(pcp))) { + try { + setActiveConfiguration(pcp, cfg); + } catch (IOException x) { + Logger.getLogger(ActiveConfigAction.class.getName()).log(Level.WARNING, null, x); + } + } + } + + public HelpCtx getHelpCtx() { + return new HelpCtx(ActiveConfigAction.class); + } + + public String getName() { + return NbBundle.getMessage(ActiveConfigAction.class, "ActiveConfigAction.label"); + } + + public void performAction() { + assert false; + } + + public Component getToolbarPresenter() { + // Do not return combo box directly; looks bad. + JPanel panel = new JPanel(new GridBagLayout()); + panel.setOpaque(false); // don't interrupt JToolBar background + panel.setMaximumSize(new Dimension(150, 80)); + panel.setMinimumSize(new Dimension(150, 0)); + panel.setPreferredSize(new Dimension(150, 23)); + // XXX top inset of 2 looks better w/ small toolbar, but 1 seems to look better for large toolbar (the default): + panel.add(configListCombo, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(1, 6, 1, 5), 0, 0)); + return panel; + } + + class ConfigMenu extends JMenu implements DynamicMenuContent { + + private final Lookup context; + + public ConfigMenu(Lookup context) { + this.context = context; + if (context != null) { + Mnemonics.setLocalizedText(this, NbBundle.getMessage(ActiveConfigAction.class, "ActiveConfigAction.context.label")); + } else { + Mnemonics.setLocalizedText(this, ActiveConfigAction.this.getName()); + } + } + + public JComponent[] getMenuPresenters() { + removeAll(); + final ProjectConfigurationProvider pcp; + if (context != null) { + Collection projects = context.lookupAll(Project.class); + if (projects.size() == 1) { + pcp = projects.iterator().next().getLookup().lookup(ProjectConfigurationProvider.class); + } else { + // No selection, or multiselection. + pcp = null; + } + } else { + pcp = ActiveConfigAction.this.pcp; // global menu item; take from main project + } + if (pcp != null) { + boolean something = false; + ProjectConfiguration activeConfig = getActiveConfiguration(pcp); + for (final ProjectConfiguration config : getConfigurations(pcp)) { + JRadioButtonMenuItem jmi = new JRadioButtonMenuItem(config.getDisplayName(), config.equals(activeConfig)); + jmi.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + activeConfigurationSelected(config); + } + }); + add(jmi); + something = true; + } + if (pcp.hasCustomizer()) { + if (something) { + addSeparator(); + } + something = true; + JMenuItem customize = new JMenuItem(); + Mnemonics.setLocalizedText(customize, NbBundle.getMessage(ActiveConfigAction.class, "ActiveConfigAction.customize")); + customize.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + pcp.customize(); + } + }); + add(customize); + } + setEnabled(something); + } else { + // No configurations supported for this project. + setEnabled(false); + // to hide entirely just use: return new JComponent[0]; + } + return new JComponent[] {this}; + } + + public JComponent[] synchMenuPresenters(JComponent[] items) { + // Always rebuild submenu. + // For performance, could try to reuse it if context == null and nothing has changed. + return getMenuPresenters(); + } + + } + + public JMenuItem getMenuPresenter() { + return new ConfigMenu(null); + } + + private static class ConfigCellRenderer extends DefaultListCellRenderer { + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value instanceof ProjectConfiguration) { + return super.getListCellRendererComponent(list, ((ProjectConfiguration) value).getDisplayName(), index, isSelected, cellHasFocus); + } else if (value == CUSTOMIZE_ENTRY) { + String label = org.openide.awt.Actions.cutAmpersand(NbBundle.getMessage(ActiveConfigAction.class, "ActiveConfigAction.customize")); + return super.getListCellRendererComponent(list, label, index, isSelected, cellHasFocus); + } else { + assert value == null; + return super.getListCellRendererComponent(list, null, index, isSelected, cellHasFocus); + } + } + } + + private synchronized void activeProjectChanged(Project p) { + if (currentProject != p) { + if (pcp != null) { + pcp.removePropertyChangeListener(lst); + } + currentProject = p; + if (currentProject != null) { + pcp = currentProject.getLookup().lookup(ProjectConfigurationProvider.class); + if (pcp != null) { + pcp.addPropertyChangeListener(lst); + } + } else { + pcp = null; + } + configurationsListChanged(pcp == null ? null : getConfigurations(pcp)); + + } + } + + public Action createContextAwareInstance(final Lookup actionContext) { + class A extends AbstractAction implements Presenter.Popup { + public void actionPerformed(ActionEvent e) { + assert false; + } + public JMenuItem getPopupPresenter() { + return new ConfigMenu(actionContext); + } + } + return new A(); + } + + private static Collection getConfigurations(final ProjectConfigurationProvider pcp) { + return ProjectManager.mutex().readAccess(new Mutex.Action>() { + public Collection run() { + return pcp.getConfigurations(); + } + }); + } + + private static ProjectConfiguration getActiveConfiguration(final ProjectConfigurationProvider pcp) { + return ProjectManager.mutex().readAccess(new Mutex.Action() { + public ProjectConfiguration run() { + return pcp.getActiveConfiguration(); + } + }); + } + + @SuppressWarnings("unchecked") + private static void setActiveConfiguration(ProjectConfigurationProvider pcp, final ProjectConfiguration pc) throws IOException { + final ProjectConfigurationProvider _pcp = pcp; + try { + ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() { + public Void run() throws IOException { + _pcp.setActiveConfiguration(pc); + return null; + } + }); + } catch (MutexException e) { + throw (IOException) e.getException(); + } + } + +} Index: projects/projectui/src/org/netbeans/modules/project/ui/actions/Bundle.properties diff -u projects/projectui/src/org/netbeans/modules/project/ui/actions/Bundle.properties:1.31 projects/projectui/src/org/netbeans/modules/project/ui/actions/Bundle.properties:1.28.12.3 --- projects/projectui/src/org/netbeans/modules/project/ui/actions/Bundle.properties:1.31 Fri Aug 18 04:35:01 2006 +++ projects/projectui/src/org/netbeans/modules/project/ui/actions/Bundle.properties Mon Aug 21 13:34:11 2006 @@ -86,9 +86,10 @@ # {0} - project count OpenProjectFolderAction.LBL_menu_multiple=Open {0} Projects +ActiveConfigAction.label=Set Main Project Con&figuration +ActiveConfigAction.context.label=Set Configuration +ActiveConfigAction.customize=&Customize... - -# # UI Logging # # UI logging of button press @@ -98,6 +99,3 @@ # {3} class of the action # {4} display name of the action UI_ACTION_BUTTON_PRESS=Invoking {4} implemented as {3} thru {1} - - - Index: projects/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java diff -u projects/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java:1.15 projects/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java:1.13.12.5 --- projects/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java:1.15 Mon Aug 7 14:24:43 2006 +++ projects/projectui/src/org/netbeans/modules/project/ui/actions/MainProjectAction.java Tue Aug 29 10:10:24 2006 @@ -19,36 +19,53 @@ package org.netbeans.modules.project.ui.actions; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; import javax.swing.Icon; import javax.swing.JButton; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.project.Project; 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.ProjectConfiguration; +import org.netbeans.spi.project.ProjectConfigurationProvider; import org.netbeans.spi.project.ui.support.ProjectActionPerformer; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.awt.Actions; import org.openide.awt.MouseUtils; +import org.openide.awt.ToolbarPool; import org.openide.util.Lookup; import org.openide.util.Mutex; import org.openide.util.NbBundle; import org.openide.util.WeakListeners; +import org.openide.util.actions.Presenter; +import org.openide.util.lookup.Lookups; /** Invokes command on the main project. * * @author Pet Hrebejk */ -public class MainProjectAction extends BasicAction implements PropertyChangeListener { +public class MainProjectAction extends BasicAction implements Presenter.Toolbar, PropertyChangeListener { private String command; private ProjectActionPerformer performer; @@ -144,7 +161,7 @@ } } - private boolean showNoMainProjectWarning (Project[] projects, String action) { + private boolean showNoMainProjectWarning(Project[] projects, String action) { boolean canceled; final JButton okButton = new JButton (NbBundle.getMessage (NoMainProjectWarning.class, "LBL_NoMainClassWarning_ChooseMainProject_OK")); // NOI18N okButton.getAccessibleContext().setAccessibleDescription (NbBundle.getMessage (NoMainProjectWarning.class, "AD_NoMainClassWarning_ChooseMainProject_OK")); @@ -191,5 +208,116 @@ return canceled; } - -} \ No newline at end of file + + private static final boolean SHOW_CONFIG_DROPDOWN = Boolean.getBoolean("org.netbeans.modules.project.ui.actions.MainProjectAction.SHOW_CONFIG_DROPDOWN"); // NOI18N + + /** + * @see org.openide.awt.Toolbar.Folder#createInstance + * @see org.openide.awt.Actions.ButtonBridge#updateButtonIcon + */ + private static final String PREFERRED_ICON_SIZE = "PreferredIconSize"; // NOI18N + public Component getToolbarPresenter() { + final JButton main = new JButton(); + Actions.connect(main, this); + if (!SHOW_CONFIG_DROPDOWN) { + return main; + } + final JPanel panel = new JPanel(new BorderLayout()); + panel.addPropertyChangeListener(PREFERRED_ICON_SIZE, new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + main.putClientProperty(PREFERRED_ICON_SIZE, panel.getClientProperty(PREFERRED_ICON_SIZE)); + } + }); + panel.setBorder(new EmptyBorder(0, 0, 0, 0)); + panel.add(main, BorderLayout.CENTER); + JButton configs = new ConfigButton(main.getPreferredSize().height); + panel.add(configs, BorderLayout.LINE_END); + return panel; + } + + private static final class ArrowIcon implements Icon { + private static final int SIZE = 6; + private static final int HEIGHT = ToolbarPool.getDefault().getPreferredIconSize(); + private static final int PAD = 3; + public int getIconWidth() { + return SIZE * 2 - 1 + PAD * 2; + } + public int getIconHeight() { + return Math.max(SIZE + PAD * 2, HEIGHT); + } + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(Color.BLACK); + int offx = PAD; + int offy = (g.getClipBounds().height - SIZE) / 2; + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.fillPolygon(new int[] {offx, offx + SIZE * 2 - 2, offx + SIZE - 1}, new int[] {offy, offy, offy + SIZE - 1}, 3); + } + } + + private final class ConfigButton extends JButton implements PropertyChangeListener, ActionListener { + + private final PropertyChangeListener pcl = WeakListeners.propertyChange(this, null); + private JPopupMenu menu; + + public ConfigButton(int height) { + super(new ArrowIcon()); + OpenProjectList.getDefault().addPropertyChangeListener(pcl); + propertyChange(null); + addActionListener(this); + setPreferredSize(new Dimension(getIcon().getIconWidth(), height)); + setFocusPainted(false); + } + + public void propertyChange(PropertyChangeEvent evt) { + String prop = evt != null ? evt.getPropertyName() : null; + if (prop == null || prop.equals(OpenProjectList.PROPERTY_MAIN_PROJECT) || prop.equals(ProjectConfigurationProvider.PROP_CONFIGURATIONS)) { + Mutex.EVENT.readAccess(new Runnable() { + public void run() { + boolean v = false; + Project p = OpenProjectList.getDefault().getMainProject(); + if (p != null) { + ActionProvider ap = p.getLookup().lookup(ActionProvider.class); + if (ap != null) { + if (Arrays.asList(ap.getSupportedActions()).contains(command)) { + ProjectConfigurationProvider pcp = p.getLookup().lookup(ProjectConfigurationProvider.class); + if (pcp != null) { + pcp.removePropertyChangeListener(pcl); + pcp.addPropertyChangeListener(pcl); + v = pcp.configurationsAffectAction(command) && + // Only show it if there are multiple configs to run. + pcp.getConfigurations().size() > 1; + } + } + } + } + setVisible(v); + } + }); + } + } + + public void actionPerformed(ActionEvent e) { + if (menu != null) { + menu.setVisible(false); + menu = null; + return; + } + final Project p = OpenProjectList.getDefault().getMainProject(); + ProjectConfigurationProvider pcp = p.getLookup().lookup(ProjectConfigurationProvider.class); + menu = new JPopupMenu(); + for (final ProjectConfiguration config : pcp.getConfigurations()) { + JMenuItem item = new JMenuItem(config.getDisplayName()); + menu.add(item); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + menu = null; + p.getLookup().lookup(ActionProvider.class).invokeAction(command, Lookups.singleton(config)); + } + }); + } + menu.show(this, 0, getSize().height); + } + + } + +} Index: projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml diff -u projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml:1.69 projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml:1.68.12.2 --- projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml:1.69 Fri Jun 30 14:27:23 2006 +++ projects/projectui/src/org/netbeans/modules/project/ui/resources/layer.xml Mon Aug 21 13:34:15 2006 @@ -131,6 +131,8 @@ + + @@ -349,8 +351,14 @@ - - + + + + + + + + @@ -489,6 +497,12 @@ + + + + + + Index: projects/projectuiapi/apichanges.xml diff -u projects/projectuiapi/apichanges.xml:1.22 projects/projectuiapi/apichanges.xml:1.21.6.2 --- projects/projectuiapi/apichanges.xml:1.22 Fri Jun 30 14:27:26 2006 +++ projects/projectuiapi/apichanges.xml Mon Aug 21 13:34:18 2006 @@ -81,6 +81,24 @@ + + + + Added CommonProjectActions.setProjectConfigurationAction + + + + + +

    + Added method CommonProjectActions.setProjectConfigurationAction() + to permit projects supporting configurations to include a context menu + item in their logical view to change the active configuration. +

    +
    + + +
    Index: projects/projectuiapi/src/org/netbeans/modules/project/uiapi/ActionsFactory.java diff -u projects/projectuiapi/src/org/netbeans/modules/project/uiapi/ActionsFactory.java:1.10 projects/projectuiapi/src/org/netbeans/modules/project/uiapi/ActionsFactory.java:1.9.38.2 --- projects/projectuiapi/src/org/netbeans/modules/project/uiapi/ActionsFactory.java:1.10 Fri Jun 30 14:27:27 2006 +++ projects/projectuiapi/src/org/netbeans/modules/project/uiapi/ActionsFactory.java Mon Aug 21 13:34:20 2006 @@ -66,5 +66,7 @@ public Action fileCommandAction( String command, String name, Icon icon ); public Action renameProjectAction(); + + Action setProjectConfigurationAction(); } Index: projects/projectuiapi/src/org/netbeans/spi/project/ui/support/CommonProjectActions.java diff -u projects/projectuiapi/src/org/netbeans/spi/project/ui/support/CommonProjectActions.java:1.14 projects/projectuiapi/src/org/netbeans/spi/project/ui/support/CommonProjectActions.java:1.13.38.3 --- projects/projectuiapi/src/org/netbeans/spi/project/ui/support/CommonProjectActions.java:1.14 Fri Jun 30 14:27:29 2006 +++ projects/projectuiapi/src/org/netbeans/spi/project/ui/support/CommonProjectActions.java Mon Aug 21 13:34:24 2006 @@ -178,4 +178,20 @@ return Utilities.getActionsFactory().newProjectAction(); } + /** + * Creates an action that sets the configuration of the selected project. + * It should be displayed with an action context containing + * exactly one {@link org.netbeans.api.project.Project}. + * The action itself should not be invoked but you may use its popup presenter. + *

    + * You might include this in the context menu of a logical view. + *

    + * @return an action + * @since org.netbeans.modules.projectuiapi/1 1.16 + * @see org.netbeans.spi.project.ProjectConfigurationProvider + */ + public static Action setProjectConfigurationAction() { + return Utilities.getActionsFactory().setProjectConfigurationAction(); + } + }