Index: apichanges.xml =================================================================== RCS file: /cvs/projects/projectuiapi/apichanges.xml,v --- apichanges.xml 22 Sep 2005 11:56:25 -0000 1.20 +++ apichanges.xml 11 Apr 2006 09:19:43 -0000 @@ -75,6 +75,25 @@ + + + + Ability to construct a project customizer from panels declared in layers. + + + + + + + Added createCustomizerDialog(String, Lookup, String, ActionListener, HelpCtx) method to ProjectCustomizer. + That allows to construct project customizer from a layer folder content. It assumes to find instances of + ProjectCustomizer.CompositeCategoryProviderin the layer and constructs the UI from them. + The context is passed into the panel via a Lookup instance. What is in the lookup, is up to the individual project + implementations. + + + + Index: nbproject/project.properties =================================================================== RCS file: /cvs/projects/projectuiapi/nbproject/project.properties,v --- nbproject/project.properties 12 Dec 2005 15:40:22 -0000 1.17 +++ nbproject/project.properties 11 Apr 2006 09:19:43 -0000 @@ -9,7 +9,7 @@ # Code is Sun Microsystems, Inc. Portions Copyright 1997-2005 Sun # Microsystems, Inc. All Rights Reserved. -spec.version.base=1.14.0 +spec.version.base=1.15.0 is.autoload=true javadoc.title=Project UI API javadoc.arch=${basedir}/arch.xml Index: src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java =================================================================== RCS file: /cvs/projects/projectuiapi/src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java,v --- src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java 5 Dec 2005 20:10:33 -0000 1.6 +++ src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java 11 Apr 2006 09:19:43 -0000 @@ -16,6 +16,13 @@ import java.awt.Dialog; import java.awt.Image; import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JPanel; @@ -26,7 +33,14 @@ import org.netbeans.modules.project.uiapi.CustomizerPane; import org.netbeans.modules.project.uiapi.Utilities; import org.netbeans.spi.project.ui.support.ProjectCustomizer.Category; +import org.openide.cookies.InstanceCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileStateInvalidException; +import org.openide.filesystems.Repository; +import org.openide.loaders.DataFolder; +import org.openide.loaders.DataObject; import org.openide.util.HelpCtx; +import org.openide.util.Lookup; /** Support for creating dialogs which can be used as project * customizers. The dialog may display multiple panels or categories. @@ -76,6 +90,48 @@ Dialog dialog = CustomizerDialog.createDialog( okOptionListener, innerPane, helpCtx, categories ); return dialog; } + + /** + * Creates standard customizer dialog that can be used for implementation of + * {@link org.netbeans.spi.project.ui.CustomizerProvider} based on content of a folder in Layers. + * Use this method when you want to allow composition and 3rd party additions to your customizer UI. + * You don't need to call pack() method on the dialog. The resulting dialog will + * be non-modal.
+ * Call show() on the dialog to make it visible. If you want the dialog to be + * closed after user presses the "OK" button you have to call hide() and dispose() on it. + * (Usually in the actionPerformed(...) method of the listener + * you provided as a parameter. In case of the click on the "Cancel" button + * the dialog will be closed automatically. + * @param folderPath the path in the System Filesystem that is used as root for panel composition. + * The content of the folder is assummed to be {@link org.netbeans.spi.project.ui.support.ProjectCustomizer.CompositeCategoryProvider} instances + * @param context the context for the panels, up to the project type what the context shall be, for example org.netbeans.api.project.Project instance + * @param preselectedCategory name of one of the supplied categories or null. + * Category with given name will be selected. If null + * or if the category of given name does not exist the first category will + * be selected. + * @param okOptionListener listener which will be notified when the user presses + * the OK button. + * @param helpCtx Help context for the dialog, which will be used when the + * panels in the customizer do not specify their own help context. + * @return standard project customizer dialog. + */ + public static Dialog createCustomizerDialog( String folderPath, + Lookup context, + String preselectedCategory, + ActionListener okOptionListener, + HelpCtx helpCtx) { + FileObject root = Repository.getDefault().getDefaultFileSystem().findResource(folderPath); + if (root == null) { + throw new IllegalArgumentException("The designated path " + folderPath + " doesn't exist. Cannot create customizer."); + } + DataFolder def = DataFolder.findFolder(root); + assert def != null : "Cannot find DataFolder for " + folderPath; + DelegateCategoryProvider prov = new DelegateCategoryProvider(def, context); + return createCustomizerDialog(prov.getSubCategories(), + prov, + preselectedCategory, okOptionListener, helpCtx); + + } /** Creates standard innerPane for customizer dialog. */ @@ -112,6 +168,7 @@ } } } + /** Provides components for categories. */ @@ -124,6 +181,24 @@ JComponent create( Category category ); } + + /** + * Interface for creation of Customizer categories and their respective UI panels. + * Implementations are to be registered in System FileSystem via module layers. Used by the + * {@link org.netbeans.spi.project.ui.support.ProjectCustomizer#createCustomizerDialog(String,Lookup,String,ActionListener,HelpCtx)} + */ + public static interface CompositeCategoryProvider { + + /** + * create the Category instance for the given project customizer context. + */ + Category createCategory( Lookup context ); + + /** + * create the UI component for given category and context. + */ + JComponent createComponent (Category category, Lookup context ); + } /** Describes category of properties to be customized by given component */ @@ -246,5 +321,82 @@ } } - + + private static class DelegateCategoryProvider implements CategoryComponentProvider, CompositeCategoryProvider { + private Lookup context; + private HashMap category2provider; + private DataFolder folder; + public DelegateCategoryProvider(DataFolder folder, Lookup context) { + this(folder, context, new HashMap()); + } + + private DelegateCategoryProvider(DataFolder folder, Lookup context, HashMap cat2Provider) { + this.context = context; + this.folder = folder; + category2provider = cat2Provider; + } + + public JComponent create(ProjectCustomizer.Category category) { + CompositeCategoryProvider prov = (CompositeCategoryProvider)category2provider.get(category); + assert prov != null : "Category doesn't have a provider associated."; + return prov.createComponent(category, context); + } + + public ProjectCustomizer.Category[] getSubCategories() { + try { + return readCategories(folder); + } catch (IOException exc) { + Logger.getAnonymousLogger().log(Level.WARNING, "Cannot construct Project UI panels", exc); + return new ProjectCustomizer.Category[0]; + } catch (ClassNotFoundException ex) { + Logger.getAnonymousLogger().log(Level.WARNING, "Cannot construct Project UI panels", ex); + return new ProjectCustomizer.Category[0]; + } + } + + + private ProjectCustomizer.Category[] readCategories(DataFolder folder) throws IOException, ClassNotFoundException { + List toRet = new ArrayList(); + DataObject[] dobjs = folder.getChildren(); + for (int i = 0; i < dobjs.length; i++) { + if (dobjs[i] instanceof DataFolder) { + CompositeCategoryProvider prov = new DelegateCategoryProvider((DataFolder)dobjs[i], context, category2provider); + ProjectCustomizer.Category cat = prov.createCategory(context); + toRet.add(cat); + category2provider.put(cat, prov); + } + InstanceCookie cook = (InstanceCookie)dobjs[i].getCookie(InstanceCookie.class); + if (cook != null && CompositeCategoryProvider.class.isAssignableFrom(cook.instanceClass())) { + CompositeCategoryProvider provider = (CompositeCategoryProvider)cook.instanceCreate(); + ProjectCustomizer.Category cat = provider.createCategory(context); + if (cat != null) { + toRet.add(cat); + category2provider.put(cat, provider); + } + } + } + return (ProjectCustomizer.Category[])toRet.toArray(new ProjectCustomizer.Category[toRet.size()]); + } + + /** + * provides category for folder.. + */ + public ProjectCustomizer.Category createCategory(Lookup context) { + FileObject fo = folder.getPrimaryFile(); + String dn = fo.getNameExt(); + try { + dn = fo.getFileSystem().getStatus().annotateName(fo.getNameExt(), Collections.singleton(fo)); + } catch (FileStateInvalidException ex) { + Logger.getAnonymousLogger().log(Level.WARNING, "Cannot retrieve display name for folder " + fo.getPath(), ex); + } + return ProjectCustomizer.Category.create(folder.getName(), dn, null, getSubCategories()); + } + + /** + * provides component for folder category + */ + public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) { + return new JPanel(); + } + } }