? src/org/netbeans/modules/project/uiapi/SavingProjectDataPanel.form ? src/org/netbeans/modules/project/uiapi/SavingProjectDataPanel.java ? test/unit/src/org/netbeans/spi/project/ui/support/ProjectCustomizerListenersTest.java Index: apichanges.xml =================================================================== RCS file: /cvs/projects/projectuiapi/apichanges.xml,v retrieving revision 1.32 diff -u -r1.32 apichanges.xml --- apichanges.xml 10 May 2007 07:54:56 -0000 1.32 +++ apichanges.xml 27 Aug 2007 08:20:43 -0000 @@ -81,6 +81,23 @@ + + + + Added methods for creating customizer UI with additional listener for saving outside of AWT EQ + + + + + + New methods were added to allow to use additional listener for saving data after user presses OK button + on Project Customzer UI. The listener will be executed after all OkListeners and will be executed off the + AWT Event Queue. During save operation modal dialog with progress bar is displayed. + + + + + Add LookupMergerimplementation for ProjectOpenedHook @@ -96,6 +113,7 @@ + Adding template attribute project.license Index: nbproject/project.xml =================================================================== RCS file: /cvs/projects/projectuiapi/nbproject/project.xml,v retrieving revision 1.20 diff -u -r1.20 project.xml --- nbproject/project.xml 1 May 2007 21:41:13 -0000 1.20 +++ nbproject/project.xml 27 Aug 2007 08:20:43 -0000 @@ -104,43 +104,49 @@ 7.8 + + org.openide.windows + + + + 6.16 + + - - - unit - - org.netbeans.modules.progress.ui - - - org.netbeans.modules.projectuiapi - - - - - org.netbeans.modules.projectapi - - - - - org.openide.util - - - - org.openide.options - - - org.openide.windows - - - org.netbeans.modules.projectui - - - org.netbeans.modules.masterfs - - - + + + unit + + org.netbeans.modules.progress.ui + + + org.netbeans.modules.projectuiapi + + + + + org.netbeans.modules.projectapi + + + + + org.openide.util + + + + org.openide.options + + + org.openide.windows + + + org.netbeans.modules.projectui + + + org.netbeans.modules.masterfs + + - org.netbeans.api.project.ui org.netbeans.spi.project.ui Index: src/org/netbeans/modules/project/uiapi/Bundle.properties =================================================================== RCS file: /cvs/projects/projectuiapi/src/org/netbeans/modules/project/uiapi/Bundle.properties,v retrieving revision 1.16 diff -u -r1.16 Bundle.properties --- src/org/netbeans/modules/project/uiapi/Bundle.properties 10 Aug 2006 12:45:24 -0000 1.16 +++ src/org/netbeans/modules/project/uiapi/Bundle.properties 27 Aug 2007 08:20:44 -0000 @@ -131,3 +131,7 @@ ERR_Project_Name_Must_Entered=Project name must be entered. ERR_Location_Read_Only=Project location is read only. ERR_Not_Valid_Filename=Project name "{0}" is not a valid filename. + +LBL_savingDataLabel=Saving Project data ... +LBL_Saving_Project_data=Saving Project data +LBL_Saving_Project_data_progress=Saving Project data Index: src/org/netbeans/modules/project/uiapi/CustomizerDialog.java =================================================================== RCS file: /cvs/projects/projectuiapi/src/org/netbeans/modules/project/uiapi/CustomizerDialog.java,v retrieving revision 1.12 diff -u -r1.12 CustomizerDialog.java --- src/org/netbeans/modules/project/uiapi/CustomizerDialog.java 23 Mar 2007 09:13:16 -0000 1.12 +++ src/org/netbeans/modules/project/uiapi/CustomizerDialog.java 27 Aug 2007 08:20:44 -0000 @@ -20,6 +20,9 @@ package org.netbeans.modules.project.uiapi; import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; @@ -33,7 +36,12 @@ import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.spi.project.ui.support.ProjectCustomizer; @@ -44,6 +52,8 @@ import org.openide.util.Lookup; import org.openide.util.Mutex; import org.openide.util.NbBundle; +import org.openide.util.RequestProcessor; +import org.openide.windows.WindowManager; /** Implementation of standard customizer dialog. * @@ -63,7 +73,7 @@ private static final String COMMAND_OK = "OK"; // NOI18N private static final String COMMAND_CANCEL = "CANCEL"; // NOI18N - public static Dialog createDialog( ActionListener okOptionListener, final CustomizerPane innerPane, + public static Dialog createDialog( ActionListener okOptionListener, ActionListener storeListener, final CustomizerPane innerPane, HelpCtx helpCtx, final ProjectCustomizer.Category[] categories, //#97998 related ProjectCustomizer.CategoryComponentProvider componentProvider ) { @@ -89,7 +99,7 @@ // RegisterListener - ActionListener optionsListener = new OptionListener( okOptionListener, categories , componentProvider); + ActionListener optionsListener = new OptionListener(okOptionListener, storeListener, categories , componentProvider); options[ OPTION_OK ].addActionListener( optionsListener ); options[ OPTION_CANCEL ].addActionListener( optionsListener ); @@ -134,7 +144,7 @@ } } }); - + return dialog; } @@ -159,12 +169,14 @@ private static class OptionListener implements ActionListener { private ActionListener okOptionListener; + private ActionListener storeListener; private ProjectCustomizer.Category[] categories; private Lookup.Provider prov; - OptionListener( ActionListener okOptionListener, ProjectCustomizer.Category[] categs, + OptionListener( ActionListener okOptionListener, ActionListener storeListener, ProjectCustomizer.Category[] categs, ProjectCustomizer.CategoryComponentProvider componentProvider) { this.okOptionListener = okOptionListener; + this.storeListener = storeListener; categories = categs; //#97998 related if (componentProvider instanceof Lookup.Provider) { @@ -174,44 +186,105 @@ public void actionPerformed( final ActionEvent e ) { String command = e.getActionCommand(); - + if ( COMMAND_OK.equals( command ) ) { // Call the OK option listener ProjectManager.mutex().writeAccess(new Mutex.Action() { public Object run() { okOptionListener.actionPerformed( e ); // XXX maybe create new event actionPerformed(e, categories); - //#97998 related - if (prov != null) { - Project prj = prov.getLookup().lookup(Project.class); - if (ProjectManager.getDefault().isModified(prj)) { - try { - ProjectManager.getDefault().saveProject(prj); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } catch (IllegalArgumentException ex) { - Exceptions.printStackTrace(ex); + return null; + } + }); + + final ProgressHandle handle = ProgressHandleFactory.createHandle(NbBundle.getMessage(CustomizerDialog.class, "LBL_Saving_Project_data_progress")); + JComponent component = ProgressHandleFactory.createProgressComponent(handle); + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + final JDialog dialog = new JDialog(mainWindow, + NbBundle.getMessage(CustomizerDialog.class, "LBL_Saving_Project_data"), true); + SavingProjectDataPanel panel = new SavingProjectDataPanel(component); + + dialog.getContentPane().add(panel); + dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + dialog.pack(); + + Rectangle bounds = mainWindow.getBounds(); + int middleX = bounds.x + bounds.width / 2; + int middleY = bounds.y + bounds.height / 2; + Dimension size = dialog.getPreferredSize(); + dialog.setBounds(middleX - size.width / 2, middleY - size.height / 2, size.width, size.height); + + // Call storeListeners out of AWT EQ + RequestProcessor.getDefault().post(new Runnable() { + public void run() { + try { + ProjectManager.mutex().writeAccess(new Mutex.Action() { + public Object run() { + handle.start(); + if (storeListener != null) { + storeListener.actionPerformed(e); + } + storePerformed(e, categories); + // #97998 related + saveModifiedProject(); + return null; + } + }); + } finally { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + dialog.setVisible(false); + dialog.dispose(); } - } + }); } - return null; } }); + + dialog.setVisible(true); + } } private void actionPerformed(ActionEvent e, ProjectCustomizer.Category[] categs) { - for (int i = 0; i < categs.length; i++) { - ActionListener list = categs[i].getOkButtonListener(); + for (ProjectCustomizer.Category category : categs) { + ActionListener list = category.getOkButtonListener(); if (list != null) { list.actionPerformed(e);// XXX maybe create new event } - if (categs[i].getSubcategories() != null) { - actionPerformed(e, categs[i].getSubcategories()); + if (category.getSubcategories() != null) { + actionPerformed(e, category.getSubcategories()); } } } - + + private void storePerformed(ActionEvent e, ProjectCustomizer.Category[] categories) { + for (ProjectCustomizer.Category category : categories) { + ActionListener listener = category.getStoreListener(); + if (listener != null) { + listener.actionPerformed(e); // XXX maybe create new event + } + if (category.getSubcategories() != null) { + storePerformed(e, category.getSubcategories()); + } + } + } + + private void saveModifiedProject() { + if (prov != null) { + Project prj = prov.getLookup().lookup(Project.class); + if (ProjectManager.getDefault().isModified(prj)) { + try { + ProjectManager.getDefault().saveProject(prj); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } catch (IllegalArgumentException ex) { + Exceptions.printStackTrace(ex); + } + } + } + } + } private static class HelpCtxChangeListener implements PropertyChangeListener { 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 retrieving revision 1.15 diff -u -r1.15 ProjectCustomizer.java --- src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java 23 Mar 2007 09:13:16 -0000 1.15 +++ src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java 27 Aug 2007 08:20:44 -0000 @@ -93,11 +93,50 @@ String preselectedCategory, ActionListener okOptionListener, HelpCtx helpCtx ) { + return createCustomizerDialog(categories, componentProvider, preselectedCategory, okOptionListener, null, helpCtx); + } + + /** Creates standard which can be used for implementation + * of {@link org.netbeans.spi.project.ui.CustomizerProvider}. 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. + * @since org.netbeans.modules.projectuiapi/1 1.25 + * @param categories array of descriptions of categories to be shown in the + * dialog. Note that categories have the valid + * property. If any of the given categories is not valid cusomizer's + * OK button will be disabled until all categories become valid + * again. + * @param componentProvider creator of GUI components for categories in the + * customizer dialog. + * @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 storeListener listener which will be notified when the user presses OK button. + * Listener will be executed after okOptionListener outside of AWT EventQueue. + * Usually to be used to save modified files on disk. + * @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( Category[] categories, + CategoryComponentProvider componentProvider, + String preselectedCategory, + ActionListener okOptionListener, + ActionListener storeListener, + HelpCtx helpCtx ) { CustomizerPane innerPane = createCustomizerPane(categories, componentProvider, preselectedCategory); - Dialog dialog = CustomizerDialog.createDialog( okOptionListener, innerPane, helpCtx, categories, componentProvider); + Dialog dialog = CustomizerDialog.createDialog(okOptionListener, storeListener, innerPane, helpCtx, categories, componentProvider); 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. @@ -128,6 +167,44 @@ String preselectedCategory, ActionListener okOptionListener, HelpCtx helpCtx) { + return createCustomizerDialog(folderPath, context, preselectedCategory, + okOptionListener, null, helpCtx); + } + + /** + * 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. + * @since org.netbeans.modules.projectuiapi/1 1.25 + * @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 storeListener listener which will be notified when the user presses OK button. + * Listener will be executed after okOptionListener outside of AWT EventQueue. + * Usually to be used to save modified files on disk + * @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, + ActionListener storeListener, + 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."); @@ -135,10 +212,8 @@ 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); - + return createCustomizerDialog(prov.getSubCategories(), prov, preselectedCategory, + okOptionListener, storeListener, helpCtx); } /** Creates standard innerPane for customizer dialog. @@ -238,6 +313,7 @@ private boolean valid; private String errorMessage; private ActionListener okListener; + private ActionListener storeListener; /** Private constructor. See the factory method. */ @@ -367,6 +443,27 @@ */ public ActionListener getOkButtonListener() { return okListener; + } + + /** + * Set the action listener that will get notified when the changes in the customizer + * are to be applied. Listener is executed after OkButtonListener outside of AWT EventQueue. + * Usually to be used to save modified files on disk. + * @param listener ActionListener to notify + * @since org.netbeans.modules.projectuiapi/1 1.25 + */ + public void setStoreListener(ActionListener listener) { + storeListener = listener; + } + + /** + * Returns the action listener that is executed outside of AWT EQ and is associated + * with this category that gets notified when OK button is pressed on the customizer. + * @return instance of ActionListener or null if not set. + * @since org.netbeans.modules.projectuiapi/1 1.25 + */ + public ActionListener getStoreListener() { + return storeListener; } }